The Divine Flock - DaedalusDock Flockmind Port (#31984)

* Initial commit - FLOCKMIND - Probably has like a billion things to fix

* Do after conversions

* Config

* Moved the files, icon fixes

* Tick everything, language work, event, spawn landmark, role prefs, beginning mob port

* Spans and some other fixes. Also the tickening

* More tickening

* More fixes. Lots of fixes.

* More Fixes

* A whole lot more. Also flock TGUI.

* Fixes fixes fixes fixes fixes

* FIXES

* More fixes - PR ready, still needs a fuckton of testing

* Fixes

* fix incomplete upstream merge

* fix FlockPanel + sort button name

* TGUI review

* Fixes tealprint list

* Fixes

* More fixes

* Incapacitator Fix

* Filenames

* Linters

* Interceptor range buff

* Reagent counts

* Linters

* Fabricator vendor fix

* Keybinds and HUD - Flockdrones, Fixes Vendor Conversion, Cube Materials

* Reworks reagent, adds flock grilles, fixes compute node overlays

* Intent-based flockdrone parts

* Intent based drone parts

* Radial control panel for controlling drones manually, phasing through windows/grilles

* Movement fixes

* Radio talk power, stare fix

* Flock health HUD

* Fixes flock lights, linters

* Unit tests

* Adds countdown to relay

* Relay improvements

* Small fix

* Logic Schmogic

* Relay overlay and looping sound effect

* Ignore air when converting turfs

* Cage fixes and improvements

* Improved flock bolt

* Turret conversions

* Flock bolts taze simple or basic mobs

* Sentience type

* Fixe

* Linter

* tgui review stage 2

* Concentrated Repair Burst

* Improves radio detection

* Removes extra space

* Adds healing visual effect

* Cube tech levels

* Ghooost

* Excess

* Flock doors, chairs, lattices. Centralizes conversion code.  Crafting with Gnesis

* Update code/modules/antagonists/flockmind/ai_behaviors/flock_wander.dm

Co-authored-by: Kapu1178 <75460809+Kapu1178@users.noreply.github.com>
Signed-off-by: PollardTheDragon <144391971+PollardTheDragon@users.noreply.github.com>

* Fixes the fix

* Astar movement detection

* Fix, extraneous code, language stuff

* Language fixes and wander fix

* Fixes

* Another fix

* Lints

* Another linter

* Language improvement

* More language improvements

* Time requirement and appearing in orbit menu as an antag

* Cube glow

* TGUI

* Minicache

* Linters

* Grammar

* Material ID fix

* Lid fix

* Reagent turf reaction

* Reagent fix

* Butcher results

* Conversion rates

* Flock stare fix

* Fixes stare behavior

* Staring

* Flock mob blood

* Flock mobs gibs and blood. Also some runtime fixes

* Flock mobs now resist out of grabs, buckles, lockers, and more

* Fixes flock orbit, fixes a runtime I think,

* Target mechs, damage mechs, other bug fixes

* Cage fix

* Cage resist change

* Some mind changes, gatecrash buff

* Drones now shoot mechs, stare improvement

* Cut down on spam a little

* Nest fix

* No more resist spam

* Fixed drone death control

* Resist statement

* Makes the relay alarm scarier

* Fixes dead flock camera mobs having no ghost sprite, something with ghosting

* Enhanced flockphasing

* Improved flockmob pathing

* Added required turf restriction to relay

* Increased needed bandwidth for relay construction

* Nerfed drone substrate rate

* Added new status tab items for relay progress

* Another relay cost adjustment

* Improves drone AI responsiveness

* Computer frames now become flock computers

* Improves target finding for conversion, building, and replicating

* Reduced flock event pop requirements

* Adjusts flock protection on structures. Adjusts overlays.

* Relay unlock tweak

* Fixwes flock being able to gib mech'd AIs with one button

* Map conflict

* Flock can no longer be outed by merely existing

* Fied bug causing drones to shoot themselves

* Prevents mobs from attacking while in a cage

* Converter tool can now open closets and crates.

* Adds descriptions to flockdrone tools.

* More informatic blurbs

* Adds xenobiology organs

* Organ lint

* TGUI merge

* bundle and mm

---------

Signed-off-by: PollardTheDragon <144391971+PollardTheDragon@users.noreply.github.com>
Co-authored-by: Toastical <20125180+Toastical@users.noreply.github.com>
Co-authored-by: Kapu1178 <75460809+Kapu1178@users.noreply.github.com>
Co-authored-by: Burzah <116982774+Burzah@users.noreply.github.com>
This commit is contained in:
PollardTheDragon
2026-07-08 17:31:28 -04:00
committed by GitHub
parent ab05a4ee3a
commit 24845b238a
228 changed files with 9724 additions and 49 deletions
+5
View File
@@ -45581,6 +45581,7 @@
/obj/structure/chair/office/dark{
dir = 8
},
/obj/effect/landmark/spawner/flock,
/turf/simulated/floor/plasteel/dark,
/area/station/science/server)
"ecJ" = (
@@ -48979,6 +48980,7 @@
c_tag = "Telecommunications Core";
dir = 4
},
/obj/effect/landmark/spawner/flock,
/turf/simulated/floor/plasteel/dark,
/area/station/telecomms/chamber)
"fAH" = (
@@ -55356,6 +55358,7 @@
/obj/effect/turf_decal/delivery/white/hollow,
/obj/effect/turf_decal/tiles/department/command,
/obj/machinery/ai_node/processing_node,
/obj/effect/landmark/spawner/flock,
/turf/simulated/floor/plasteel/dark,
/area/station/science/server/coldroom)
"iqM" = (
@@ -82024,6 +82027,7 @@
/obj/machinery/atmospherics/pipe/simple/hidden/scrubbers{
dir = 6
},
/obj/effect/landmark/spawner/flock,
/turf/simulated/floor/plasteel/dark,
/area/station/command/server)
"uhl" = (
@@ -85136,6 +85140,7 @@
/obj/effect/turf_decal/delivery/white/hollow,
/obj/effect/turf_decal/tiles/department/command,
/obj/machinery/ai_node/network_node,
/obj/effect/landmark/spawner/flock,
/turf/simulated/floor/plasteel/dark,
/area/station/science/server/coldroom)
"vJm" = (
+4
View File
@@ -34,6 +34,7 @@
dir = 6
},
/obj/machinery/ai_node/network_node,
/obj/effect/landmark/spawner/flock,
/turf/simulated/floor/plasteel/dark/telecomms{
icon_state = "bcircuit"
},
@@ -3422,6 +3423,7 @@
/obj/machinery/camera/autoname{
dir = 4
},
/obj/effect/landmark/spawner/flock,
/turf/simulated/floor/plasteel{
icon_state = "bcircuit"
},
@@ -30308,6 +30310,7 @@
icon_state = "4-8"
},
/obj/machinery/hologram/holopad,
/obj/effect/landmark/spawner/flock,
/turf/simulated/floor/plasteel/dark,
/area/station/science/server)
"fmI" = (
@@ -66815,6 +66818,7 @@
dir = 5
},
/obj/machinery/ai_node/processing_node,
/obj/effect/landmark/spawner/flock,
/turf/simulated/floor/plasteel/dark/telecomms{
icon_state = "bcircuit"
},
@@ -27566,6 +27566,7 @@
/obj/structure/cable{
icon_state = "1-2"
},
/obj/effect/landmark/spawner/flock,
/turf/simulated/floor/bluegrid,
/area/station/telecomms/chamber)
"cgw" = (
@@ -44022,6 +44023,7 @@
/area/station/science/server)
"dwk" = (
/obj/machinery/atmospherics/unary/vent_pump/on,
/obj/effect/landmark/spawner/flock,
/turf/simulated/floor/plasteel/dark,
/area/station/science/server)
"dwl" = (
@@ -44634,6 +44636,7 @@
},
/obj/machinery/light/small,
/obj/machinery/ai_node/network_node,
/obj/effect/landmark/spawner/flock,
/turf/simulated/floor/bluegrid{
icon_state = "gcircuit";
name = "Mainframe Floor";
@@ -44655,6 +44658,7 @@
dir = 9
},
/obj/machinery/ai_node/processing_node,
/obj/effect/landmark/spawner/flock,
/turf/simulated/floor/bluegrid{
icon_state = "gcircuit";
name = "Mainframe Floor";
@@ -28608,6 +28608,7 @@
dir = 5
},
/obj/machinery/ai_node/processing_node,
/obj/effect/landmark/spawner/flock,
/turf/simulated/floor/plasteel/dark,
/area/station/science/server/coldroom)
"fHd" = (
@@ -38914,6 +38915,7 @@
"hJb" = (
/obj/structure/sign/poster/official/random/directional/east,
/obj/machinery/tcomms/core/station,
/obj/effect/landmark/spawner/flock,
/turf/simulated/floor/bluegrid{
icon_state = "gcircuit"
},
@@ -78818,6 +78820,7 @@
dir = 4
},
/obj/machinery/hologram/holopad,
/obj/effect/landmark/spawner/flock,
/turf/simulated/floor/plasteel/dark,
/area/station/science/server)
"pGM" = (
@@ -113511,6 +113514,7 @@
dir = 6
},
/obj/machinery/ai_node/network_node,
/obj/effect/landmark/spawner/flock,
/turf/simulated/floor/plasteel/dark,
/area/station/science/server/coldroom)
"wAw" = (
+37 -33
View File
@@ -36903,6 +36903,7 @@
icon_state = "1-2"
},
/obj/machinery/tcomms/core/station,
/obj/effect/landmark/spawner/flock,
/turf/simulated/floor/bluegrid,
/area/station/telecomms/chamber)
"gam" = (
@@ -38425,23 +38426,6 @@
/obj/effect/spawner/random/fungus/frequent,
/turf/simulated/wall,
/area/station/maintenance/fore2)
"gBa" = (
/obj/machinery/atmospherics/pipe/manifold/hidden{
dir = 8
},
/obj/machinery/atmospherics/pipe/simple/hidden/scrubbers,
/obj/machinery/atmospherics/pipe/manifold/hidden/supply{
dir = 4
},
/obj/structure/cable{
icon_state = "2-8"
},
/obj/structure/cable{
icon_state = "1-2"
},
/obj/machinery/light/floor,
/turf/simulated/floor/plasteel/dark,
/area/station/science/server)
"gBj" = (
/obj/machinery/atmospherics/pipe/simple/hidden/supply{
dir = 4
@@ -40308,20 +40292,6 @@
},
/turf/simulated/floor/plating,
/area/station/maintenance/aft)
"hdR" = (
/obj/machinery/atmospherics/pipe/simple/heat_exchanging{
dir = 10
},
/obj/machinery/firealarm/directional/east,
/obj/effect/turf_decal/siding{
dir = 8
},
/obj/machinery/ai_node/processing_node,
/obj/machinery/light{
dir = 1
},
/turf/simulated/floor/bluegrid/telecomms/server,
/area/station/science/server/coldroom)
"hdX" = (
/obj/structure/cable{
icon_state = "4-8"
@@ -58367,6 +58337,24 @@
/obj/effect/turf_decal/tiles/dark/checker,
/turf/simulated/floor/plasteel,
/area/station/medical/break_room)
"mXX" = (
/obj/machinery/atmospherics/pipe/manifold/hidden{
dir = 8
},
/obj/machinery/atmospherics/pipe/simple/hidden/scrubbers,
/obj/machinery/atmospherics/pipe/manifold/hidden/supply{
dir = 4
},
/obj/structure/cable{
icon_state = "2-8"
},
/obj/structure/cable{
icon_state = "1-2"
},
/obj/effect/landmark/spawner/flock,
/obj/machinery/light/floor,
/turf/simulated/floor/plasteel/dark,
/area/station/science/server)
"mYa" = (
/obj/structure/grille/broken,
/obj/effect/mapping_helpers/turfs/damage,
@@ -68606,6 +68594,7 @@
dir = 4
},
/obj/machinery/ai_node/network_node,
/obj/effect/landmark/spawner/flock,
/turf/simulated/floor/bluegrid/telecomms/server,
/area/station/science/server/coldroom)
"quh" = (
@@ -68908,6 +68897,21 @@
/obj/effect/spawner/random/barrier/grille_maybe,
/turf/simulated/floor/plating,
/area/station/maintenance/fore)
"qyJ" = (
/obj/machinery/firealarm/directional/east,
/obj/machinery/ai_node/processing_node,
/obj/effect/landmark/spawner/flock,
/obj/machinery/atmospherics/pipe/simple/heat_exchanging{
dir = 10
},
/obj/effect/turf_decal/siding{
dir = 8
},
/obj/machinery/light{
dir = 1
},
/turf/simulated/floor/bluegrid/telecomms/server,
/area/station/science/server/coldroom)
"qyM" = (
/obj/effect/decal/cleanable/dirt,
/obj/effect/spawner/random/barrier/grille_often,
@@ -128405,7 +128409,7 @@ cmB
cbf
cyS
mHT
gBa
mXX
mdH
apS
kDU
@@ -128659,7 +128663,7 @@ cte
cte
cte
cmB
hdR
qyJ
xcw
cyP
dcs
+10
View File
@@ -3,6 +3,13 @@
#define HAS_AI_CONTROLLER_TYPE(thing, type) istype(thing?.ai_controller, type)
//AI controller flags
#ifdef DEBUG_AI
#define DEBUG_AI_LOG(controller, message) if(controller.debug_focus) { to_chat(world, span_debug("AI DBG: [message]")) }
#else
#define DEBUG_AI_LOG(...)
#endif
//If you add a new status, be sure to add it to the ai_controllers subsystem's ai_controllers_by_status list.
/// The AI is currently active.
#define AI_STATUS_ON "ai_on"
@@ -51,6 +58,9 @@
/// Does this behavior NOT block planning?
#define AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION (1<<4)
/// Magic value to tell the G.O.A.P selector to ignore a behavior.
#define AI_GOAP_SKIP_BEHAVIOR (-1000001)
// AI flags
/// Don't move if being pulled
+4
View File
@@ -22,6 +22,10 @@
#define BB_BASIC_MOB_FLEE_DISTANCE "BB_BASIC_FLEE_DISTANCE"
#define DEFAULT_BASIC_FLEE_DISTANCE 9
/// Path we should use next time we use the JPS movement datum
#define BB_PATH_TO_USE "BB_path_to_use"
/// Max path length we should use next time we use the JPS movement datum
#define BB_PATH_MAX_LENGTH "BB_path_max_length"
// Searching
/// key holding a range to look for stuff in
+22
View File
@@ -0,0 +1,22 @@
// Flockmind Keys]
#define BB_FLOCK_STARE_TARGET "BB_flock_stare_target"
#define BB_FLOCK_WANDERING "BB_flock_wandering"
#define BB_FLOCK_STARE_CD "BB_flock_stare_cooldown"
#define BB_FLOCK_CONVERT_TARGET "BB_flock_convert_target"
#define BB_FLOCK_DECON_TARGET "BB_flock_decon_target"
#define BB_FLOCK_HARVEST_TARGET "BB_flock_harvest_target"
#define BB_FLOCK_WANDER_FRUSTRATION "BB_flock_wander_frustration"
#define BB_FLOCK_HEAL_TARGET "BB_flock_heal_target"
#define BB_FLOCK_HEAL_FRUSTRATION "BB_flock_heal_frustation"
#define BB_FLOCK_REPLICATE_TARGET "BB_flock_replicate_target"
#define BB_FLOCK_NEST_TARGET "BB_flock_nest_target"
#define BB_FLOCK_OVERMIND_CONTROL "BB_flock_overmind_control"
#define BB_FLOCK_CONTAINER_TARGET "BB_flock_container_target"
#define BB_FLOCK_RUMMAGE_TARGET "BB_flock_rummage_target"
#define BB_FLOCK_CAPTURE_TARGET "BB_flock_capture_target"
#define BB_FLOCK_ATTACK_TARGET "BB_flock_attack_target"
#define BB_FLOCK_ATTACK_COOLDOWN "BB_flock_attack_cooldown"
#define BB_FLOCK_ATTACK_RUN_COOLDOWN "BB_flock_attack_run_cooldown"
#define BB_FLOCK_ATTACK_STRAFE_COOLDOWN "BB_flock_attack_strafe_cooldown"
#define BB_FLOCK_ATTACK_FRUSTRATION "BB_flock_attack_frustration"
#define BB_FLOCK_DEPOSIT_TARGET "BB_flock_deposit_target"
+1
View File
@@ -120,6 +120,7 @@
#define COLOR_BLOOD_BASE "#A10808"
#define COLOR_BLOOD_MACHINE "#1F181F"
#define COLOR_BLOOD_FLOCK "#4d736d"
#define COLOR_BLOOD_XENO "#05EE05"
// Pipe colours
+2
View File
@@ -85,6 +85,8 @@
#define MAT_IRIDIUM "iridium"
#define MAT_SILK "silk"
#define MAT_CARDBOARD "cardboard"
#define MAT_GNESIS "gnesis"
#define MAT_GNESIS_GLASS "gnesisglass"
/// The amount of materials you get from a sheet of mineral like iron/diamond/glass etc
#define MINERAL_MATERIAL_AMOUNT 2000
+3
View File
@@ -20,3 +20,6 @@
#define COMSIG_AI_CONTROLLER_PICKED_BEHAVIORS "ai_controller_picked_behaviors"
///sent from ai controllers when they stop possessing a pawn: (datum/ai_controller/source_controller)
#define COMSIG_AI_CONTROLLER_UNPOSSESSED_PAWN "ai_controller_unpossessed_pawn"
/// Signal sent when a mob's AI turns on or off.
#define COMSIG_AI_STATUS_CHANGE "ai_status_change"
+2
View File
@@ -9,6 +9,8 @@
#define COMSIG_MOVELOOP_POSTPROCESS "moveloop_postprocess"
//from [/datum/move_loop/has_target/jps/recalculate_path] ():
#define COMSIG_MOVELOOP_JPS_REPATH "moveloop_jps_repath"
//from [/datum/move_loop/has_target/astar/recalculate_path] ():
#define COMSIG_MOVELOOP_ASTAR_REPATH "moveloop_astar_repath"
///From base of /datum/move_loop/process() after attempting to move a movable: (datum/move_loop/loop, old_dir)
#define COMSIG_MOVABLE_MOVED_FROM_LOOP "movable_moved_from_loop"
///from [/datum/move_loop/has_target/jps/on_finish_pathing]
+4
View File
@@ -0,0 +1,4 @@
#define COMSIG_FLOCK_PROTECTION_TRIGGER "comsig_flock_protection_trigger"
#define COMPONENT_FLOCK_DENY_ATTACK (1<<0)
#define COMSIG_TURF_CLAIMED_BY_FLOCK "comsig_turf_claimed_by_flock"
+4 -1
View File
@@ -148,6 +148,8 @@
#define PASSGIRDER (1<<8)
#define PASSTAKE (1<<9)
#define PASSBARRICADE (1<<10)
/// Pass through flock objects and mobs
#define PASSFLOCK (1<<11)
//turf-only flags, under the flags variable
#define BLESSED_TILE (1<<0)
@@ -185,8 +187,9 @@
#define ZAP_MOB_DAMAGE (1<<3)
#define ZAP_MOB_STUN (1<<4)
#define ZAP_GENERATES_POWER (1<<5)
#define ZAP_NO_COOLDOWN (1<<6)
#define ZAP_DEFAULT_FLAGS (ZAP_MOB_STUN | ZAP_MOB_DAMAGE | ZAP_OBJ_DAMAGE)
#define ZAP_DEFAULT_FLAGS (ZAP_MOB_STUN | ZAP_MOB_DAMAGE | ZAP_OBJ_DAMAGE | ZAP_NO_COOLDOWN)
#define ZAP_FUSION_FLAGS (ZAP_OBJ_DAMAGE | ZAP_MOB_DAMAGE | ZAP_MOB_STUN)
#define ZAP_SUPERMATTER_FLAGS (ZAP_GENERATES_POWER)
+70
View File
@@ -0,0 +1,70 @@
#define FLOCK_TYPE_OVERMIND "overmind"
#define FLOCK_TYPE_TRACE "trace"
#define FLOCK_TYPE_DRONE "drone"
#define FLOCK_TYPE_BIT "bit"
// Turf reserved by a flockdrone
#define FLOCK_NOTICE_RESERVED "reserved"
// Turf marked for conversion
#define FLOCK_NOTICE_PRIORITY "priority"
// Mob marked as enemy
#define FLOCK_NOTICE_ENEMY "enemy"
// Mob marked as ignored
#define FLOCK_NOTICE_IGNORE "ignore"
// Flockdrone under flocktrace control
#define FLOCK_NOTICE_FLOCKTRACE_CONTROL "flocktrace_control"
// Flockdrone under overmind control
#define FLOCK_NOTICE_FLOCKMIND_CONTROL "flockmind_control"
// Flockdrone health bars
#define FLOCK_NOTICE_HEALTH "flock_health"
// Atom marked for deconstruction
#define FLOCK_NOTICE_DECONSTRUCT "flock_deconstruct"
#define FLOCK_UI_DRONES "drones"
#define FLOCK_UI_TRACES "traces"
#define FLOCK_UI_ENEMIES "enemies"
#define FLOCK_UI_STRUCTURES "structures"
#define FLOCK_COMPUTE_COST_FLOCKTRACE 100
#define FLOCK_COMPUTE_COST_DRONE 10
#define FLOCK_COMPUTE_COST_RELAY 750
#define FLOCK_TURFS_FOR_RELAY 250
/// Amount of substrate to add to a tealprint.
#define FLOCK_SUBSTRATE_COST_DEPOST_TEALPRINT 10
/// Amount to convert a turf and it's contents.
#define FLOCK_SUBSTRATE_COST_CONVERT 20
/// Amount to repair a flock construct.
#define FLOCK_SUBSTRATE_COST_REPAIR 10
/// BASE amount to lay an egg.
#define FLOCK_SUBSTRATE_COST_LAY_EGG 100
/// Egg cost does not start scaling until there are this many drones.
#define FLOCK_MIN_DESIRED_POP 10
/// Each drone above the min desired pop adds this much to the substrate required to be able to lay an egg.
#define FLOCK_ADDITIONAL_RESOURCE_RESERVATION_PER_DRONE 8
#define FLOCK_DRONE_LIMIT 50
#define FLOCK_ENDGAME_LOST 1
#define FLOCK_ENDGAME_RELAY_BUILT 2
#define FLOCK_ENDGAME_RELAY_ACTIVATING 3
#define FLOCK_ENDGAME_VICTORY 4
/// The list of behaviors for the planners to select from. THIS IS ACTUALLY A SUBKEY!
#define BB_PLANNER_BEHAVIORS "BB_planner_behaviors"
// G.O.A.P weights for flock behaviors, for easy comparison/adjustment.
#define FLOCK_BEHAVIOR_WEIGHT_WANDER 1
#define FLOCK_BEHAVIOR_WEIGHT_STARE 1
#define FLOCK_BEHAVIOR_WEIGHT_CONVERT 1
#define FLOCK_BEHAVIOR_WEIGHT_HARVEST 2
#define FLOCK_BEHAVIOR_WEIGHT_OPEN_CONTAINER 3
#define FLOCK_BEHAVIOR_WEIGHT_RUMMAGE 3
#define FLOCK_BEHAVIOR_WEIGHT_REPAIR 4
#define FLOCK_BEHAVIOR_WEIGHT_NEST 6
#define FLOCK_BEHAVIOR_WEIGHT_REPLICATE 7
#define FLOCK_BEHAVIOR_WEIGHT_DECONSTRUCT 8
#define FLOCK_BEHAVIOR_WEIGHT_DEPOSIT 8
#define FLOCK_BEHAVIOR_WEIGHT_SHOOT 10
#define FLOCK_BEHAVIOR_WEIGHT_CAPTURE 15
+1
View File
@@ -67,6 +67,7 @@
#define SPECIAL_ROLE_HERETIC "Heretic"
#define SPECIAL_ROLE_EVENTMISC "Event Role"
#define SPECIAL_ROLE_NINJA "Space Ninja"
#define SPECIAL_ROLE_FLOCK "Divine Flock"
#define SPECIAL_ROLE_IRRADIATED_MOUSE "Irradiated Mouse"
// Constants used by code which checks the status of nuclear blasts during a
+18
View File
@@ -31,6 +31,22 @@
#define isalienqueen(A) (istype(A, /mob/living/carbon/alien/humanoid/queen))
#define isaicamera(A) (istype(A, /mob/camera/ai_eye))
#define isflockmob(A) (istype(A, /mob/camera/flock) || istype(A, /mob/living/basic/flock))
#define isflockdrone(A) (istype(A, /mob/living/basic/flock/drone))
#define isflockbit(A) (istype(A, /mob/living/basic/flock/bit))
#define isflocktrace(A) (istype(A, /mob/camera/flock/trace))
#define isflockmind(A) (istype(A, /mob/camera/flock/overmind))
#define isflockcontroller(A) (isflockmind(A) || isflocktrace(A))
#define isflockworker(A) (isflockdrone(A) || isflockbit(A))
// Simple animals
#define isanimal(A) (istype(A, /mob/living/simple_animal))
@@ -134,6 +150,8 @@ GLOBAL_LIST_INIT(glass_sheet_types, typecacheof(list(
#define is_ancient_rock(A) (istype(A, /turf/simulated/mineral/ancient))
#define isflockturf(A) (istype(A, /turf/simulated/floor/flock) || istype(A, /turf/simulated/wall/flock))
// Areas
//#define isarea(A, B, C...) BYOND proc, can test multiple arguments and only return TRUE if all are areas
+2
View File
@@ -133,6 +133,8 @@
#define FLOOR_OPENSPACE_PLANE 22
#define OPENSPACE_LAYER 22
#define INFO_TAG_PLANE 23
#define BYOND_LIGHTING_PLANE 24
#define BYOND_LIGHTING_LAYER 24
+5
View File
@@ -190,6 +190,8 @@
#define SHOCK_ILLUSION (1<<2)
///The shock doesn't stun.
#define SHOCK_NOSTUN (1<<3)
/// Shock damage is reduced by the average siemen's coeff
#define SHOCK_USE_AVG_SIEMENS (1 << 4)
#define POCKET_STRIP_DELAY 4 SECONDS //time taken to search somebody's pockets
@@ -233,6 +235,9 @@
#define DIRECTION_LOCK_SLOWDOWN 3
// Helpers
#define DOING_INTERACTION(user, interaction_key) (LAZYACCESS(user.do_afters, interaction_key))
//Human sub-species
#define isabductor(A) (is_species(A, /datum/species/abductor))
#define isgolem(A) (is_species(A, /datum/species/golem))
+2
View File
@@ -46,6 +46,7 @@
#define ROLE_GHOST "ghost role"
#define ROLE_ELITE "lavaland elite"
#define ROLE_NINJA "ninja"
#define ROLE_FLOCK "flockmind"
#define ROLE_IRRADIATED_MOUSE "irradiated mouse"
#define ROLE_UPLIFTED_PRIMITIVE "uplifted primitive"
@@ -78,6 +79,7 @@ GLOBAL_LIST_INIT(special_roles_antags, list(
ROLE_IRRADIATED_MOUSE,
ROLE_UPLIFTED_PRIMITIVE,
ROLE_FLOCK,
// UNUSED/BROKEN ANTAGS
// ROLE_HOG_GOD = /datum/game_mode/hand_of_god,
// ROLE_HOG_CULTIST = /datum/game_mode/hand_of_god,
+1
View File
@@ -15,6 +15,7 @@ GLOBAL_LIST_INIT(antag_roles, list(
ROLE_MORPH,
ROLE_TSPIDER,
ROLE_NINJA,
ROLE_FLOCK,
))
// Bannable other roles
+3
View File
@@ -15,6 +15,9 @@
#define INVISIBILITY_HIDDEN_RUNES 30
#define SEE_INVISIBLE_HIDDEN_RUNES 30
#define INVISIBILITY_FLOCK 30
#define SEE_INVISIBLE_FLOCK 30
#define SEE_INVISIBLE_LEVEL_ONE 35 //Used by some stuff in code. It's really poorly organized.
#define SEE_INVISIBLE_LEVEL_TWO 45 //Used by some other stuff in code. It's really poorly organized.
+2
View File
@@ -130,3 +130,5 @@
#define SPAN_WHISPER(str) ("<span class='whisper'>" + str + "</span>")
#define SPAN_ZOMBIE(str) ("<span class='zombie'>" + str + "</span>")
#define SPAN_ZOMBIELARGE(str) ("<span class='zombielarge'>" + str + "</span>")
#define SPAN_FLOCKSAY(str) ("<span class='flocksay'>" + str + "</span>")
#define SPAN_FLOCKSAY_GRADIENT(str) ("<span>[gradient_text(str, "#3cb5a3", "#1e806e")]</span>")
+118
View File
@@ -0,0 +1,118 @@
/// A copy of is_blocked_turf(), ignoring flock mobs
/turf/proc/can_flock_occupy(atom/source_atom)
if(density)
return FALSE
for(var/atom/movable/movable_content as anything in contents)
// We don't want to block ourselves or consider any ignored atoms.
if((movable_content == source_atom) || isflockmob(movable_content))
continue
// If the thing is dense AND we're including mobs or the thing isn't a mob AND if there's a source atom and
// it cannot pass through the thing on the turf, we consider the turf blocked.
if(movable_content.density)
if(source_atom && movable_content.CanPass(source_atom, get_dir(src, source_atom)))
continue
return FALSE
return TRUE
/proc/pointer_image_to(atom/from, atom/towards, color = "#00ff9dff")
var/image/pointer = image(icon = 'icons/mob/screen_gen.dmi', icon_state = "arrow_greyscale", loc = from)
pointer.plane = HUD_PLANE
pointer.appearance_flags |= RESET_COLOR
pointer.color = color
var/angle = 180 + get_angle(from, towards)
var/matrix/final_matrix = pointer.transform.Scale(2,2)
final_matrix = final_matrix.Turn(angle)
pointer.transform = final_matrix
pointer.pixel_x = sin(angle) * -48
pointer.pixel_y = cos(angle) * -48
return pointer
/proc/flock_realname(flock_type)
var/attempts = 0
var/name = ""
do
attempts++
switch(flock_type)
if(FLOCK_TYPE_OVERMIND)
name = "\proper[pick(GLOB.consonants_lower)][pick(GLOB.vowels_lower)].[pick(GLOB.consonants_lower)][pick(GLOB.vowels_lower)]"
if(FLOCK_TYPE_TRACE)
name = "[pick(GLOB.consonants_upper)][pick(GLOB.vowels_lower)].[pick(GLOB.vowels_lower)]"
if(FLOCK_TYPE_DRONE)
name = "\proper[pick(GLOB.consonants_lower)][pick(GLOB.vowels_lower)].[pick(GLOB.consonants_lower)][pick(GLOB.vowels_lower)].[pick(GLOB.consonants_lower)][pick(GLOB.vowels_lower)]"
if(FLOCK_TYPE_BIT)
name = "[pick(GLOB.consonants_upper)].[rand(10,99)].[rand(10,99)]"
while(attempts <= 10 && findname(name))
return name
/proc/flock_name(flock_type)
var/attempts = 0
var/name = ""
do
attempts++
switch(flock_type)
if(FLOCK_TYPE_DRONE)
name = "[pick(GLOB.flockdrone_name_adjectives)] [pick(GLOB.flockdrone_name_nouns)]"
if(FLOCK_TYPE_BIT)
name = "[pick(GLOB.consonants_upper)].[rand(10,99)].[rand(10,99)]"
while(attempts <= 10 && findname(name))
return name
/proc/animate_flockpass(atom/thing)
var/list/color_matrix = list(1,0,0, 0,1,0, 0,0,1, 0.15,0.77,0.66)
var/matrix/shrink = thing.transform.Scale(0.4)
var/old_transform = thing.transform
animate(thing, color = color_matrix, transform = shrink, time = 3, easing=BOUNCE_EASING)
animate(color = null, transform = old_transform, time = 3, easing=BOUNCE_EASING)
/// Make a color a repeating gradient between two colors. Note: This is inaccurate because its a linear transformation, but human eyes do not perceive color this way.
/proc/gradient_text(message, color_1, color_2)
var/list/color_list_1 = rgb2num(color_1)
var/list/color_list_2 = rgb2num(color_2)
var/r1 = color_list_1[1]
var/g1 = color_list_1[2]
var/b1 = color_list_1[3]
// The difference in value between each color part
var/delta_r = color_list_2[1] - r1
var/delta_g = color_list_2[2] - g1
var/delta_b = color_list_2[3] - b1
var/list/result = list()
// Start at a random point between the two, in increments of 0.1
var/coeff = rand(0,10) / 10.0
var/dir = prob(50) ? -1 : 1
for(var/i in 1 to length(message) step 3)
coeff += dir * 0.2
// 20% chance to start going in the opposite direction
if(prob(20))
dir = -dir
// Wrap back around
if(coeff < 0)
coeff = 0
dir = 1
else if(coeff > 1)
coeff = 1
dir = -1
var/col = rgb(r1 + delta_r*coeff, g1 + delta_g*coeff, b1 + delta_b*coeff)
var/chars = copytext(message, i, i + 3)
result += "<span style='color:[col]'>[chars]</span>"
. = jointext(result, "")
+27
View File
@@ -49,6 +49,33 @@
};\
} while(FALSE)
/// The above but the tree is sorted in reverse.
#define BINARY_INSERT_REVERSE(INPUT, LIST, TYPECONT, COMPARE, COMPARISON, COMPTYPE) \
do {\
var/list/__BIN_LIST = LIST;\
var/__BIN_CTTL = length(__BIN_LIST);\
if(!__BIN_CTTL) {\
__BIN_LIST += INPUT;\
} else {\
var/__BIN_LEFT = 1;\
var/__BIN_RIGHT = __BIN_CTTL;\
var/__BIN_MID = (__BIN_LEFT + __BIN_RIGHT) >> 1;\
var ##TYPECONT/__BIN_ITEM;\
while(__BIN_LEFT < __BIN_RIGHT) {\
__BIN_ITEM = COMPTYPE;\
if(__BIN_ITEM.##COMPARISON >= COMPARE.##COMPARISON) {\
__BIN_LEFT = __BIN_MID + 1;\
} else {\
__BIN_RIGHT = __BIN_MID;\
};\
__BIN_MID = (__BIN_LEFT + __BIN_RIGHT) >> 1;\
};\
__BIN_ITEM = COMPTYPE;\
__BIN_MID = __BIN_ITEM.##COMPARISON < COMPARE.##COMPARISON ? __BIN_MID : __BIN_MID + 1;\
__BIN_LIST.Insert(__BIN_MID, INPUT);\
};\
} while(FALSE)
#define SORT_FIRST_INDEX(list) (list[1])
#define SORT_COMPARE_DIRECTLY(thing) (thing)
#define SORT_VAR_NO_TYPE(varname) var/varname
+276
View File
@@ -0,0 +1,276 @@
#define ASTAR_NODE(turf, dist_from_start, heuristic, prev_node) list(turf, dist_from_start + heuristic, dist_from_start, heuristic, prev_node)
#define ASTAR_CLOSE_ENOUGH_TO_END(end, checking_turf) (end == checking_turf || (mintargetdist && (get_dist(checking_turf, end) <= mintargetdist)))
/datum/astar_node
var/turf/turf
var/total_cost_f
var/dist_from_start_g
/// Distance_g is affected by bias to smooth out diagonals, this tracks the raw number of steps required.
var/real_dist_from_start
var/heuristic_h
var/datum/astar_node/prev_node
/datum/pathfind/astar
/// The thing that we're actually trying to path for
var/atom/movable/invoker
/// The turf we're trying to path to (note that this won't track a moving target)
var/turf/end
/// The list we compile at the end if successful to pass back
var/list/path
/// A k:v list of turf -> directions. The directions are directions the pathfinder attempted to step into the turf but failed.
var/list/closed
/// A binary search tree containing the discovered nodes.
var/list/open_binary_tree
/// A k:V list of turf -> astar node
var/list/open_turf_to_node
/// How far away we have to get to the end target before we can call it quits
var/mintargetdist = 0
/// If we should delete the first step in the path or not. Used often because it is just the starting tile
var/skip_first = FALSE
/// Defines how we handle diagonal moves. See __DEFINES/path.dm
var/use_diagonals = TRUE
/// An optional callback to invoke to return a positive value to add to the path's distance.
var/datum/callback/heuristic
#ifdef DEBUG_PATHFINDING
/// List of all nodes we've parsed, used for debug spew.
var/list/all_nodes_ever = list()
#endif
/datum/pathfind/astar/New(
atom/movable/invoker,
atom/goal,
access,
max_steps,
mintargetdist,
simulated_only,
avoid,
skip_first,
use_diagonals,
datum/callback/on_finish,
datum/callback/heuristic,
)
src.invoker = invoker
src.pass_info = new(invoker, access)
end = get_turf(goal)
open_binary_tree = new()
open_turf_to_node = new()
closed = new()
src.max_distance = max_steps
src.mintargetdist = mintargetdist
src.simulated_only = simulated_only
src.avoid = avoid
src.skip_first = skip_first
src.use_diagonals = use_diagonals
src.on_finish = on_finish
src.heuristic = heuristic || CALLBACK(src, PROC_REF(generic_heuristic))
/datum/pathfind/astar/Destroy(force, ...)
. = ..()
invoker = null
end = null
open_binary_tree = null
open_turf_to_node = null
closed = null
heuristic = null // hard del generator if using generic_heuristic
/**
* "starts" off the pathfinding, by storing the values this datum will need to work later on
* returns FALSE if it fails to setup properly, TRUE otherwise
*/
/datum/pathfind/astar/start()
start ||= get_turf(invoker)
. = ..()
if(!.)
return .
if(!get_turf(end))
stack_trace("Invalid A* destination")
return FALSE
if(start.z != end.z || start == end) //no pathfinding between z levels
return FALSE
// If the turf is out of the step range we already know it's too far.
if(max_distance && (max_distance < get_dist_manhattan(start, end)))
return FALSE
var/datum/astar_node/start_node = new /datum/astar_node()
start_node.turf = start
start_node.total_cost_f = 0
start_node.dist_from_start_g = 0
start_node.heuristic_h = 0
start_node.real_dist_from_start = 0
open_turf_to_node[start] = start_node
binary_insert_node(start_node)
return TRUE
/**
* Cleanup pass for the pathfinder. This tidies up the path, and fufills the pathfind's obligations
*/
/datum/pathfind/astar/finished()
var/list/path = src.path || list()
if(length(path) > 0 && skip_first)
path.Cut(1,2)
hand_back(path)
#ifdef DEBUG_PATHFINDING
/// If the global flag is set, replace the current global node spew.
if(GLOB.__pathfinding_debug_generate)
GLOB.__pathfinding_debug_info = all_nodes_ever
#endif
return ..()
/**
* search_step() is the workhorse of pathfinding. It'll do the searching logic, and will slowly build up a path
* returns TRUE if everything is stable, FALSE if the pathfinding logic has failed, and we need to abort
*/
/datum/pathfind/astar/search_step(tick_check = TRUE)
. = ..()
if(!.)
return .
if(QDELETED(invoker))
return FALSE
var/static/list/lateral_search_dirs = list(EAST, WEST, NORTH, SOUTH)
var/static/list/all_search_dirs = list(EAST, WEST, NORTH, SOUTH, NORTHEAST, SOUTHWEST, NORTHWEST, SOUTHEAST)
while(length(open_binary_tree) && !path)
var/datum/astar_node/current_node = open_binary_tree[length(open_binary_tree)]
open_binary_tree.len--
var/turf/current_node_turf = current_node.turf
closed[current_node_turf] = ALL
if(max_distance && current_node.real_dist_from_start > max_distance)
continue
// Check to see if we're close enough to the end destination.
if(ASTAR_CLOSE_ENOUGH_TO_END(end, current_node_turf))
unwind_path(current_node)
return TRUE
// Scan cardinal turfs for valid movements.
for(var/scan_direction in use_diagonals ? all_search_dirs : lateral_search_dirs)
var/turf/searching_turf = get_step(current_node_turf, scan_direction)
var/IS_DIR_DIAGONAL = IS_DIR_DIAGONAL(scan_direction)
if(closed[searching_turf] & scan_direction)
continue // Turf is known to be blocked from this direction, skip!
if(!(IS_DIR_DIAGONAL ? can_step_diagonal(current_node_turf, searching_turf) : CAN_STEP(current_node_turf, searching_turf, simulated_only, pass_info, avoid)))
closed[searching_turf] |= scan_direction
continue // Turf cannot be entered, atleast from this direction. Skip!
// At this point we consider this turf a valid node.
var/datum/astar_node/existing_node = open_turf_to_node[searching_turf]
// Prefer straighter lines for more visual appeal. Penalize changing from cardinal to diagonal, but if you're already diagonal, it's okay.
var/distance_g = current_node.dist_from_start_g
var/real_distance = current_node.real_dist_from_start
if(IS_DIR_DIAGONAL)
// Diagonal is not continuing from previous node
if(!current_node.prev_node || !IS_DIR_DIAGONAL(get_dir(current_node.prev_node.turf, current_node_turf)))
distance_g += 2
real_distance += 2
// Diagonal is continuing from previous node
else
distance_g += sqrt(2) // It const folds dont cry
real_distance += 2
else
distance_g += 1
real_distance += 1
// If the node already exists, update it to reflect new information. Maybe we found a shorter path to it, or similar.
if(existing_node)
if(distance_g < existing_node.dist_from_start_g)
existing_node.prev_node = current_node
existing_node.dist_from_start_g = distance_g
existing_node.real_dist_from_start = real_distance
existing_node.total_cost_f = distance_g + existing_node.heuristic_h
open_binary_tree -= existing_node
binary_insert_node(existing_node)
continue
// The node isn't known to us so we need to check the heuristic.
var/heuristic_h = heuristic.Invoke(searching_turf, end)
if(heuristic_h == 0)
closed[searching_turf] |= scan_direction
continue
// Node is not known, create it.
var/datum/astar_node/new_node = new /datum/astar_node()
new_node.turf = searching_turf
new_node.total_cost_f = distance_g + heuristic_h
new_node.dist_from_start_g = distance_g
new_node.real_dist_from_start = real_distance
new_node.heuristic_h = heuristic_h
new_node.prev_node = current_node
binary_insert_node(new_node)
open_turf_to_node[searching_turf] = new_node
#ifdef DEBUG_PATHFINDING
all_nodes_ever[++all_nodes_ever.len] = new_node
#endif
// Check to see if we're close enough to the end destination.
if(ASTAR_CLOSE_ENOUGH_TO_END(end, new_node))
unwind_path(new_node)
return TRUE
// Stable, we'll just be back later
if(tick_check && TICK_CHECK)
return TRUE
return TRUE
/datum/pathfind/astar/proc/binary_insert_node(datum/astar_node/node)
BINARY_INSERT_REVERSE(node, open_binary_tree, /datum/astar_node, node, total_cost_f, COMPARE_KEY)
/datum/pathfind/astar/proc/can_step_diagonal(turf/from_turf, turf/to_turf)
var/in_dir = get_dir(from_turf, to_turf) // eg. northwest (1+8) = 9 (00001001)
var/first_step_direction_a = in_dir & 3 // eg. north (1+8)&3 (0000 0011) = 1 (0000 0001)
var/first_step_direction_b = in_dir & 12 // eg. west (1+8)&12 (0000 1100) = 8 (0000 1000)
for(var/direction in list(first_step_direction_a, first_step_direction_b))
var/turf/midpoint = get_step(from_turf, direction)
// If the midpoint is known to be inaccessible from the starting direction, no need to check it again.
if(closed[midpoint] & direction)
continue
if(CAN_STEP(midpoint, to_turf, simulated_only, pass_info, avoid))
return TRUE
return FALSE
/// The generic heuristic, euclidean distance.
/datum/pathfind/astar/proc/generic_heuristic(turf/searching_turf, turf/end)
return get_dist_euclidean(searching_turf, end)
/// Called when we've hit the goal with the node that represents the last tile, then sets the path var to that path so it can be returned by [datum/pathfind/proc/search]
/datum/pathfind/astar/proc/unwind_path(datum/astar_node/unwind_node)
path = new()
var/turf/iter_turf = unwind_node.turf
path += iter_turf
var/datum/astar_node/iter_node = unwind_node.prev_node
while(iter_node)
path.Insert(1, iter_node.turf)
iter_node = iter_node.prev_node
return path
#undef ASTAR_NODE
#undef ASTAR_CLOSE_ENOUGH_TO_END
+2 -2
View File
@@ -111,7 +111,7 @@
found_turfs[start] = TRUE // i'm sure this is fine
return TRUE
/datum/pathfind/jps/search_step()
/datum/pathfind/jps/search_step(tick_check = TRUE)
. = ..()
if(!.)
return .
@@ -131,7 +131,7 @@
diag_scan_spec(current_turf, scan_direction, current_processed_node)
// Stable, we'll just be back later
if(TICK_CHECK)
if(tick_check && TICK_CHECK)
return TRUE
return TRUE
+9 -3
View File
@@ -229,9 +229,9 @@
/// These are generally cheaper than looping contents so they go first
switch(destination_turf.pathing_pass_method)
// This is already assumed to be true
//if(TURF_PATHING_PASS_DENSITY)
// if(destination_turf.density)
// return TRUE
if(TURF_PATHING_PASS_DENSITY)
if(destination_turf.density)
return TRUE
if(TURF_PATHING_PASS_PROC)
if(!destination_turf.CanPathfindPass(actual_dir, pass_info))
return TRUE
@@ -309,6 +309,8 @@
var/datum/can_pass_info/rider_info = null
/// If our mob is buckled to something, what's it like
var/datum/can_pass_info/buckled_info = null
/// If our mob is flock phasing or can flock phase.
var/able_to_flockphase = FALSE
var/list/factions = list()
@@ -363,6 +365,10 @@
if(iscameramob(construct_from))
src.camera_type = construct_from.type
src.is_bot = isbot(construct_from)
if(isflockdrone(construct_from))
var/mob/living/basic/flock/drone/bird = construct_from
if(HAS_TRAIT(bird, TRAIT_FLOCKPHASE) || bird.substrate.has_points(10))
able_to_flockphase = TRUE
if(construct_from.pulling)
src.pulling_info = new(construct_from.pulling, access, no_id, call_depth + 1)
+11
View File
@@ -383,3 +383,14 @@
if(!test_turf.is_blocked_turf(exclude_mobs = TRUE))
return FALSE
return TRUE
/// Returns the manhattan distance between two atoms. Returns INFINITY if either are not on a turf, for BYOND get_dist() parity.
/proc/get_dist_manhattan(atom/A, atom/B)
if(!A.z || !B.z)
return INFINITY
if(A == B)
return -1
return abs(A.x - B.x) + abs(A.y - B.y) + abs(A.z - B.z)
+23
View File
@@ -162,6 +162,8 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai
#define TRAIT_XENO_HOST "xeno_host" //Tracks whether we're gonna be a baby alien's mummy.
#define TRAIT_SHOCKIMMUNE "shock_immunity"
#define TRAIT_TESLA_SHOCKIMMUNE "tesla_shock_immunity"
/// Are we being shocked by a flock sentinel?
#define TRAIT_SHOCKED_BY_SENTINEL "shocked_by_flock_sentinel"
#define TRAIT_TELEKINESIS "telekinesis"
#define TRAIT_RESISTHEAT "resist_heat"
#define TRAIT_RESISTHEATHANDS "resist_heat_handsonly" //For when you want to be able to touch hot things, but still want fire to be an issue.
@@ -295,7 +297,13 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai
/// Trait that prevents AI controllers from planning detached from ai_status to prevent weird state stuff.
#define TRAIT_AI_PAUSED "trait_ai_paused"
/// trait that prevents AI controllers from making new plans, but not executing plans
#define TRAIT_AI_DISABLE_PLANNING "TRAIT_AI_DISABLE_PLANNING"
/// This mob's speech is heard through walls by dead players/observers even if it has no client. Idk a better name
#define TRAIT_IMPORTANT_SPEAKER "important_speaker"
/// Ignores line of sight for the purposes of send_speech()
#define TRAIT_HEAR_THROUGH_WALLS "hear_through_walls"
//***** MIND TRAITS *****/
#define TRAIT_HOLY "is_holy" // The mob is holy in regards to religion
@@ -621,6 +629,21 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai
///every object that is currently the active storage of some client mob has this trait
#define TRAIT_ACTIVE_STORAGE "active_storage"
// Flock trait sources
/// Under control
#define FLOCK_CONTROLLED_BY_OVERMIND_SOURCE "FLOCK_CONTROLLED_BY_OVERMIND_SOURCE"
#define TRAIT_FLOCKPHASE "TRAIT_FLOCKPHASE"
/// Is a flock object. Because of type fuckery.
#define TRAIT_FLOCK_THING "TRAIT_FLOCK_THING"
/// Flockdrones cannot deconstruct this object.
#define TRAIT_FLOCK_NODECON "TRAIT_FLOCK_NODECON"
/// Implements flock_examine() proc.
#define TRAIT_FLOCK_EXAMINE "TRAIT_FLOCK_EXAMINE"
/// Trait from mob/living/update_transform()
#define UPDATE_TRANSFORM_TRAIT "update_transform"
//***** PROC WRAPPERS *****//
/// Proc wrapper of add_trait. You should only use this for callback. Otherwise, use the macro.
/proc/callback_add_trait(datum/target, trait, source)
+31
View File
@@ -404,6 +404,8 @@
moblist.Add(M)
for(var/mob/camera/blob/M in sortmob)
moblist.Add(M)
for(var/mob/camera/flock/M in sortmob)
moblist.Add(M)
return moblist
// Format a power value in W, kW, MW, or GW.
@@ -558,6 +560,35 @@ Returns 1 if the chain up to the area contains the given typepath
return starting_turf
/// returns a turf at the outer edge of a given radius
/proc/get_random_perimeter_turf(atom/origin, radius)
var/turf/origin_turf = get_turf(origin)
if(isnull(origin_turf))
return null
if(radius == 0)
return origin_turf
var/upper_x = clamp(origin_turf.x + radius, 1, world.maxx)
var/lower_x = clamp(origin_turf.x - radius, 1, world.maxx)
var/upper_y = clamp(origin_turf.y + radius, 1, world.maxy)
var/lower_y = clamp(origin_turf.y - radius, 1, world.maxy)
var/x
var/y
var/z = origin_turf.z
if(prob(50))
x = pick(lower_x, upper_x)
y = rand(lower_y, upper_y)
else
x = rand(lower_x, upper_x)
y = pick(lower_y, upper_y)
return locate(x, y ,z)
// returns turf relative to A for a given clockwise angle at set range
// result is bounded to map size
/proc/get_angle_target_turf(atom/A, angle, range)
+4
View File
@@ -1,5 +1,9 @@
GLOBAL_LIST_INIT(alphabet, list("a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v","w","x","y","z"))
GLOBAL_LIST_INIT(alphabet_uppercase, list("A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W","X","Y","Z"))
GLOBAL_LIST_INIT(vowels_lower, list("a","e","i","o","u"))
GLOBAL_LIST_INIT(vowels_upper, list("A","E","I","O","U"))
GLOBAL_LIST_INIT(consonants_lower, list("b","c","d","f","g","h","j","k","l","m","n","p","q","r","s","t","v","w","x","y","z"))
GLOBAL_LIST_INIT(consonants_upper, list("B","C","D","F","G","H","J","K","L","M","N","P","Q","R","S","T","V","W","X","Y","Z"))
GLOBAL_LIST_INIT(html_colors, list("Alice Blue","Antique White","Aqua","Aquamarine","Azure","Beige","Bisque","Black","Blanched Almond","Blue","Blue Violet",
"Brown","Burly Wood","Cadet Blue","Chartreuse","Chocolate","Coral","Cornflower Blue","Cornsilk","Crimson","Cyan",
"Dark Blue","Dark Cyan","Dark Golden Rod","Dark Gray","Dark Green","Dark Khaki","Dark Magenta","Dark Olive Green",
+1 -1
View File
@@ -111,7 +111,7 @@
var/obj/tgvehicle/sealed/car/clowncar/cc = loc
return cc.fire_cannon_at(A, src, params)
if(restrained())
if(restrained() | istype(loc, /obj/structure/flock/cage))
RestrainedClickOn(A)
return
+12
View File
@@ -658,6 +658,18 @@ so as to remain in compliance with the most up-to-date laws."
return
infected_user.ghostize()
/atom/movable/screen/alert/ghost/flock
desc = "Would you like to ghost? You will be notified when your body is removed from the cage."
/atom/movable/screen/alert/ghost/flock/Click()
if(!..())
return
var/mob/living/carbon/human/caged_user = usr
if(!istype(caged_user) || caged_user.stat == DEAD)
caged_user.clear_alert("ghost_cage")
return
caged_user.ghostize()
/atom/movable/screen/alert/notify_action
name = "Body created"
desc = "A body was created. You can enter it."
+187
View File
@@ -0,0 +1,187 @@
/datum/hud/flockdrone/New(mob/owner)
. = ..()
var/atom/movable/screen/using
using = new /atom/movable/screen/flockdrone_part/converter(null, src)
static_inventory += using
using = new /atom/movable/screen/flockdrone_part/incapacitator(null, src)
static_inventory += using
using = new /atom/movable/screen/flockdrone_part/absorber(null, src)
static_inventory += using
using = new /atom/movable/screen/flockphasing()
using.icon_state = (HAS_TRAIT(mymob, TRAIT_FLOCKPHASE) ? "running" : "walking")
infodisplay += using
using = new /atom/movable/screen/act_intent/flock()
using.icon_state = mymob.a_intent
static_inventory += using
action_intent = using
mymob.healths = new /atom/movable/screen/healths/flockdrone_health(null, src)
infodisplay += mymob.healths
mymob.pullin = new /atom/movable/screen/pull/flock()
mymob.pullin.hud = src
mymob.pullin.update_icon(UPDATE_ICON_STATE)
hotkeybuttons += mymob.pullin
zone_select = new /atom/movable/screen/zone_sel/flock()
zone_select.hud = src
zone_select.update_icon(UPDATE_OVERLAYS)
static_inventory += zone_select
// Used for flock traces and the overmind
/datum/hud/flockghost
/atom/movable/screen/healths/flockdrone_health
icon = 'icons/goonstation/hud/flock_ui.dmi'
icon_state = "health1"
screen_loc = UI_BORG_HEALTH
/atom/movable/screen/pull/flock
icon = 'icons/goonstation/hud/flock_ui.dmi'
icon_state = "pull0"
screen_loc = UI_MOVI
/atom/movable/screen/act_intent/flock
icon = 'icons/goonstation/hud/flock_ui.dmi'
/atom/movable/screen/act_intent/flock/Click(location, control, params)
var/_x = text2num(params2list(params)["icon-x"])
var/_y = text2num(params2list(params)["icon-y"])
if(_x<=16 && _y<=16)
usr.a_intent_change(INTENT_GRAB)
else if(_x<=16 && _y>=17)
usr.a_intent_change(INTENT_HELP)
else if(_x>=17 && _y<=16)
usr.a_intent_change(INTENT_HARM)
else if(_x>=17 && _y>=17)
usr.a_intent_change(INTENT_DISARM)
/atom/movable/screen/flockphasing
icon = 'icons/goonstation/hud/flock_ui.dmi'
name = "flockphase toggle"
icon_state = "walking"
screen_loc = UI_MOVI
/atom/movable/screen/flockphasing/Click(location, control, params)
var/mob/living/basic/flock/drone/M = usr
if(!istype(M))
return
if(HAS_TRAIT(M, TRAIT_FLOCKPHASE))
M.stop_flockphase()
else
if(M.can_flockphase() && M.flockphase_tax())
M.start_flockphase()
/atom/movable/screen/zone_sel/flock
icon = 'icons/goonstation/hud/flock_ui.dmi'
overlay_file = 'icons/goonstation/hud/flock_ui.dmi'
/atom/movable/screen/flockdrone_part
icon = 'icons/goonstation/hud/flock_ui.dmi'
var/active_state = ""
var/inactive_state = ""
var/part_type
var/datum/flockdrone_part/part_ref
/atom/movable/screen/flockdrone_part/Initialize(mapload, datum/hud/hud_owner)
. = ..()
var/mob/living/basic/flock/drone/drone = usr
for(var/datum/flockdrone_part/part in drone.parts)
if(istype(part, part_type))
part_ref = part
break
part_ref?.screen_obj = src
update_appearance(UPDATE_ICON_STATE)
/atom/movable/screen/flockdrone_part/Destroy()
part_ref?.screen_obj = null
part_ref = null
return ..()
/atom/movable/screen/flockdrone_part/update_icon_state()
if(part_ref?.is_active())
icon_state = active_state
else
icon_state = inactive_state
return ..()
/atom/movable/screen/flockdrone_part/Click(location, control, params)
. = ..()
if(.)
return
var/mob/living/basic/flock/drone/drone = usr
drone.set_active_part(part_ref)
/atom/movable/screen/flockdrone_part/converter
name = "converter"
active_state = "converter1"
inactive_state = "converter0"
desc = "Converters are an integrated multitool in every flockdrone's arsenal. On help and disarm intent, it will build structures, open lockers, and convert turfs. On harm or grab intent, it will deconstruct or cage things."
screen_loc = "CENTER-1:16,SOUTH:5"
part_type = /datum/flockdrone_part/converter
/atom/movable/screen/flockdrone_part/incapacitator
name = "incapacitator"
active_state = "incapacitor1"
inactive_state = "incapacitor0"
desc = "Incapacitators are the primary weapon system of every flockdrone. They fire enhanced stunning rounds capable of disrupting electronics. They will recharge slowly on their own."
maptext_x = 6
maptext_y = 6
screen_loc = "CENTER:16,SOUTH:5"
part_type = /datum/flockdrone_part/incapacitator
var/icon/overlay_mask
var/obj/effect/abstract/charge_overlay
/atom/movable/screen/flockdrone_part/incapacitator/Initialize(mapload, datum/hud/hud_owner)
charge_overlay = new()
. = ..()
charge_overlay.vis_flags = VIS_INHERIT_ID | VIS_INHERIT_LAYER | VIS_INHERIT_PLANE | VIS_INHERIT_ICON
charge_overlay.icon_state = "charge_overlay"
managed_vis_overlays += charge_overlay
overlay_mask = icon('icons/goonstation/hud/flock_ui.dmi', "darkener")
charge_overlay.add_filter("mask", 1, alpha_mask_filter(0, 0, overlay_mask))
/atom/movable/screen/flockdrone_part/incapacitator/Destroy()
managed_vis_overlays -= charge_overlay
QDEL_NULL(charge_overlay)
return ..()
/atom/movable/screen/flockdrone_part/incapacitator/update_appearance(updates)
. = ..()
var/datum/flockdrone_part/incapacitator/part = part_ref
maptext = MAPTEXT("[part?.shot_count || "0"]")
var/datum/flockdrone_part/incapacitator/weapon = part_ref
charge_overlay.transition_filter(
"mask",
0.5 SECONDS,
list(
"y" = -24 * (1 - round(weapon.shot_count / weapon.max_shots, 0.1))
),
SINE_EASING,
FALSE
)
/atom/movable/screen/flockdrone_part/absorber
name = "material decompiler"
active_state = "absorber"
inactive_state = "absorber"
desc = "Absorbers are a key piece of any flockdrone's toolkit. They will pickup items and deconstruct them for substrate - the primary building material of any flock device."
screen_loc = "CENTER+1:16,SOUTH:5"
part_type = /datum/flockdrone_part/absorber
+5
View File
@@ -260,6 +260,11 @@
icon_state = "agentbox"
alpha = 128
/atom/movable/screen/fullscreen/flock_convert
icon = 'icons/goonstation/hud/flockmindcircuit.dmi'
icon_state = "flockmindcircuit"
layer = BLIND_LAYER
#undef FULLSCREEN_LAYER
#undef BLIND_LAYER
#undef CRIT_LAYER
+2
View File
@@ -10,6 +10,7 @@ SUBSYSTEM_DEF(mobs)
var/list/currentrun = list()
var/static/list/clients_by_zlevel[][]
var/static/list/dead_players_by_zlevel[][] = list(list()) // Needs to support zlevel 1 here, MaxZChanged only happens when CC is created and new_players can login before that.
var/static/list/flock_cameras_by_zlevel[][]= list(list())
var/static/list/cubemonkeys = list()
/// The amount of Xenobiology mobs (and their offspring) that exist in the world. Used for mob capping. Excludes Slimes
var/xenobiology_mobs = 0
@@ -26,6 +27,7 @@ SUBSYSTEM_DEF(mobs)
/datum/controller/subsystem/mobs/Initialize()
clients_by_zlevel = new /list(world.maxz, 0)
dead_players_by_zlevel = new /list(world.maxz, 0)
flock_cameras_by_zlevel = new /list(world.maxz, 0)
/datum/controller/subsystem/mobs/fire(resumed = 0)
var/seconds = wait * 0.1
@@ -407,6 +407,219 @@
return MOVELOOP_FAILURE
/**
* Used for following A* defined paths.
*
* Returns TRUE if the loop sucessfully started, or FALSE if it failed
*
* Arguments:
* moving - The atom we want to move
* chasing - The atom we want to move towards
* delay - How many deci-seconds to wait between fires. Defaults to the lowest value, 0.1
* repath_delay - How often we're allowed to recalculate our path
* max_path_length - The maximum number of steps we can take in a given path to search (default: 30, 0 = infinite)
* miminum_distance - Minimum distance to the target before path returns, could be used to get near a target, but not right to it - for an AI mob with a gun, for example
* id - An ID card representing what access we have and what doors we can open
* simulated_only - Whether we consider turfs without atmos simulation (AKA do we want to ignore space)
* avoid - If we want to avoid a specific turf, like if we're a mulebot who already got blocked by some turf
* skip_first - Whether or not to delete the first item in the path. This would be done because the first item is the starting tile, which can break things
* timeout - Time in deci-seconds until the moveloop self expires. Defaults to infinity
* subsystem - The movement subsystem to use. Defaults to SSmovement. Only one loop can exist for any one subsystem
* priority - Defines how different move loops override each other. Lower numbers beat higher numbers, equal defaults to what currently exists. Defaults to MOVEMENT_DEFAULT_PRIORITY
* flags - Set of bitflags that effect move loop behavior in some way. Check _DEFINES/movement.dm
*
**/
/datum/move_manager/proc/astar_move(
moving,
chasing,
delay,
timeout,
repath_delay,
max_path_length,
minimum_distance,
list/access,
simulated_only,
turf/avoid,
skip_first,
subsystem,
priority,
flags,
datum/extra_info,
initial_path,
use_diagonals,
datum/callback/heuristic,
)
return add_to_loop(
moving,
subsystem,
/datum/move_loop/has_target/astar,
priority,
flags,
extra_info,
delay,
timeout,
chasing,
repath_delay,
max_path_length,
minimum_distance,
access,
simulated_only,
avoid,
skip_first,
initial_path,
use_diagonals,
heuristic,
)
/datum/move_loop/has_target/astar
///How often we're allowed to recalculate our path
var/repath_delay
///Max amount of steps to search
var/max_path_length
///Minimum distance to the target before path returns
var/minimum_distance
///A list representing what access we have and what doors we can open.
var/list/access
///Whether we consider turfs without atmos simulation (AKA do we want to ignore space)
var/simulated_only
///A perticular turf to avoid
var/turf/avoid
///Should we skip the first step? This is the tile we're currently on, which breaks some things
var/skip_first
///A list for the path we're currently following
var/list/movement_path
/// Should we use diagonals, or use only cardinals?
var/use_diagonals
///Cooldown for repathing, prevents spam
COOLDOWN_DECLARE(repath_cooldown)
///Bool used to determine if we're already making a path in AStar. this prevents us from re-pathing while we're already busy.
var/is_pathing = FALSE
///Callback to invoke once we make a path
var/datum/callback/on_finish_callback
///AStar heuristic function
var/datum/callback/heuristic
/datum/move_loop/has_target/astar/New(datum/movement_packet/owner, datum/controller/subsystem/movement/controller, atom/moving, priority, flags, datum/extra_info)
. = ..()
on_finish_callback = CALLBACK(src, PROC_REF(on_finish_pathing))
/datum/move_loop/has_target/astar/setup(delay, timeout, atom/chasing, repath_delay, max_path_length, minimum_distance, list/access, simulated_only, turf/avoid, skip_first, list/initial_path, use_diagonals, datum/callback/heuristic)
. = ..()
if(!.)
return
src.repath_delay = repath_delay
src.max_path_length = max_path_length
src.minimum_distance = minimum_distance
src.access = access
src.simulated_only = simulated_only
src.avoid = avoid
src.skip_first = skip_first
src.use_diagonals = use_diagonals
src.heuristic = heuristic
movement_path = initial_path?.Copy()
/datum/move_loop/has_target/astar/compare_loops(
datum/move_loop/loop_type,
priority,
flags,
extra_info,
delay,
timeout,
atom/chasing,
repath_delay,
max_path_length,
minimum_distance,
obj/item/card/id/id,
simulated_only,
turf/avoid,
skip_first,
initial_path,
use_diagonals,
datum/callback/heuristic,
)
if(..() && \
repath_delay == src.repath_delay && \
max_path_length == src.max_path_length && \
minimum_distance == src.minimum_distance && \
access ~= src.access && \
simulated_only == src.simulated_only && \
avoid == src.avoid && \
use_diagonals == src.use_diagonals && \
heuristic == src.heuristic)
return TRUE
return FALSE
/datum/move_loop/has_target/astar/loop_started()
. = ..()
if(!movement_path)
INVOKE_ASYNC(src, PROC_REF(recalculate_path))
/datum/move_loop/has_target/astar/loop_stopped()
. = ..()
movement_path = null
/datum/move_loop/has_target/astar/Destroy()
avoid = null
return ..()
///Tries to calculate a new path for this moveloop.
/datum/move_loop/has_target/astar/proc/recalculate_path()
if(!COOLDOWN_FINISHED(src, repath_cooldown))
return
COOLDOWN_START(src, repath_cooldown, repath_delay)
var/path_ok = SSpathfinder.astar_pathfind(
moving,
target,
max_path_length,
minimum_distance,
access,
simulated_only,
avoid,
skip_first,
use_diagonals = use_diagonals,
on_finish = on_finish_callback,
heuristic = heuristic
)
if(path_ok)
is_pathing = TRUE
SEND_SIGNAL(src, COMSIG_MOVELOOP_ASTAR_REPATH)
///Called when a path has finished being created
/datum/move_loop/has_target/astar/proc/on_finish_pathing(list/path)
movement_path = path
is_pathing = FALSE
/datum/move_loop/has_target/astar/move()
if(!length(movement_path))
if(is_pathing)
return MOVELOOP_NOT_READY
else
INVOKE_ASYNC(src, PROC_REF(recalculate_path))
return MOVELOOP_FAILURE
var/turf/next_step = movement_path[1]
var/atom/old_loc = moving.loc
//KAPU NOTE: WE DO NOT HAVE THIS
//moving.Move(next_step, get_dir(moving, next_step), FALSE, !(flags & MOVEMENT_LOOP_NO_DIR_UPDATE))
var/movement_dir = get_dir(moving, next_step)
moving.Move(next_step, movement_dir)
. = (old_loc != moving?.loc) ? MOVELOOP_SUCCESS : MOVELOOP_FAILURE
// this check if we're on exactly the next tile may be overly brittle for dense objects who may get bumped slightly
// to the side while moving but could maybe still follow their path without needing a whole new path
if(get_turf(moving) == next_step)
if(length(movement_path))
movement_path.Cut(1,2)
else
INVOKE_ASYNC(src, PROC_REF(recalculate_path))
return MOVELOOP_FAILURE
///Base class of move_to and move_away, deals with the distance and target aspect of things
/datum/move_loop/has_target/dist_bound
var/distance = 0
@@ -213,3 +213,74 @@ SUBSYSTEM_DEF(pathfinder)
active_pathing += path
return TRUE
return FALSE
/datum/controller/subsystem/pathfinder/proc/astar_pathfind(
atom/movable/invoker,
atom/end,
max_steps = 30,
mintargetdist,
list/access,
simulated_only = TRUE,
turf/exclude,
skip_first = TRUE,
use_diagonals = TRUE,
datum/callback/on_finish,
datum/callback/heuristic,
)
var/datum/pathfind/astar/path = new(
invoker,
end,
access,
max_steps,
mintargetdist,
simulated_only,
exclude,
skip_first,
use_diagonals,
on_finish,
heuristic,
)
if(path.start())
active_pathing += path
return TRUE
return FALSE
/datum/controller/subsystem/pathfinder/proc/astar_pathfind_now(
atom/movable/invoker,
atom/end,
max_steps = 14,
mintargetdist,
list/access,
simulated_only = TRUE,
turf/exclude,
skip_first = TRUE,
use_diagonals = TRUE,
datum/callback/heuristic,
)
var/datum/pathfind/astar/path = new(
invoker,
end,
access,
max_steps,
mintargetdist,
simulated_only,
exclude,
skip_first,
use_diagonals,
null,
heuristic,
)
if(!path.start())
return FALSE
if(!path.search_step(FALSE))
path.early_exit()
return FALSE
path.finished()
return path.path
+13
View File
@@ -269,6 +269,9 @@
/datum/action/cooldown/proc/PreActivate(atom/target)
if(SEND_SIGNAL(owner, COMSIG_MOB_ABILITY_STARTED, src, target) & COMPONENT_BLOCK_ABILITY_START)
return
if(!is_valid_target(target))
return
// Note, that PreActivate handles no cooldowns at all by default.
// Be sure to call StartCooldown() in Activate() where necessary.
. = Activate(target)
@@ -361,4 +364,14 @@
return stat_panel_data
/**
* Check if the target we're casting on is a valid target.
* For no-target (self cast) actions, the target being checked (cast_on) is the caster.
* For click_to_activate actions, the target being checked is the clicked atom.
*
* Return TRUE if cast_on is valid, FALSE otherwise
*/
/datum/action/cooldown/proc/is_valid_target(atom/cast_on)
return TRUE
#undef COOLDOWN_NO_DISPLAY_TIME
+135
View File
@@ -2,12 +2,19 @@
/datum/ai_behavior
/// What distance you need to be from the target to perform the action.
var/required_distance = 1
/// If >0, overrides controller.target_search_radius
var/search_radius_override = null
/// Flags for extra behavior
var/behavior_flags = NONE
/// Cooldown between actions performances, defaults to the value of
/// CLICK_CD_MELEE because that seemed like a nice standard for the speed of
/// AI behavior
var/action_cooldown = CLICK_CD_MELEE
/// A multiplier applied to the behavior's goap_score().
var/goap_weight = 1
/// Behaviors to add upon a successful setup
var/list/sub_behaviors
/// Called by the AI controller when first being added. Additional arguments
/// depend on the behavior type. For example, if the behavior involves attacking
@@ -48,6 +55,7 @@
controller.behavior_args -= type
// If this was a movement task, reset our movement target if necessary
if(!(behavior_flags & AI_BEHAVIOR_REQUIRE_MOVEMENT))
next_behavior(controller, succeeded)
return
if(behavior_flags & AI_BEHAVIOR_KEEP_MOVE_TARGET_ON_FINISH)
return
@@ -63,3 +71,130 @@
if(controller.movement_target_source != type)
return
controller.set_movement_target(type, null)
/// Returns a behavior to perform after this one, or null if continuing this one
/datum/ai_behavior/proc/next_behavior(datum/ai_controller/controller, success)
return null
/// Executed before goap_score(), to see if the behavior should even be considered.
/datum/ai_behavior/proc/goap_precondition(datum/ai_controller/controller)
return TRUE
/// Returns a numerical value that is essentially a priority for planner behaviors.
/datum/ai_behavior/proc/goap_score(datum/ai_controller/controller)
return score_distance(controller, goap_get_ideal_target(controller))
/// Returns the ideal target for this behavior.
/datum/ai_behavior/proc/goap_get_ideal_target(datum/ai_controller/controller, set_path = FALSE)
var/list/options = goap_filter_targets(controller)
return get_best_target_by_distance_score(controller, options, set_path)
/// Filter through potential targets to find real targets.
/datum/ai_behavior/proc/goap_filter_targets(datum/ai_controller/controller)
var/list/options = list()
for(var/atom/potential_target as anything in goap_get_potential_targets(controller))
if(goap_is_valid_target(controller, potential_target))
options += potential_target
return options
/// Returns a list of potential targets to filter through.
/datum/ai_behavior/proc/goap_get_potential_targets(datum/ai_controller/controller)
return list()
/// Returns TRUE if the given atom is a valid target for this behavior.
/datum/ai_behavior/proc/goap_is_valid_target(datum/ai_controller/controller, atom/target)
return TRUE
#define BINARY_INSERT_TARGET(target_list, target, score) \
do { \
var/length = length(target_list); \
if(!length) { \
target_list[target] = score; \
} else { \
var/left = 1; \
var/right = length; \
var/middle = (left + right) >> 1; \
while(left < right) { \
if(target_list[target_list[middle]] <= score) { \
left = middle + 1; \
} else { \
right = middle; \
}; \
middle = (left + right) >> 1; \
}; \
middle = target_list[target_list[middle]] > score ? middle : middle + 1; \
target_list.Insert(middle, target); \
target_list[target] = score; \
}; \
} while(FALSE)
/// Returns the best target by scoring the distance of each possible target.
/// Takes a list to insert the path into, so it can be handed back and re-used.
/datum/ai_behavior/proc/get_best_target_by_distance_score(datum/ai_controller/controller, list/targets, set_path = FALSE)
if(!length(targets))
return null
var/atom/movable/pawn = controller.pawn
var/list/access = controller.get_access()
var/list/targets_by_score = list()
var/list/reachable_targets = list()
// Sort targets by their estimated score. The last element in the lists has the highest score.
while(length(targets))
var/index = rand(1, length(targets))
var/atom/A = targets[index]
targets.Cut(index, index + 1)
var/score = score_distance(controller, A)
BINARY_INSERT_TARGET(targets_by_score, A, score)
// WEE WOO WEE WOO BEHAVIOR-CHANGING MICRO-OPT: we assume turfs further than 1 tile aren't reachable
// Because this is true in 99.9999999999999999% of cases
if(get_dist(pawn, A) <= 1 && A.Adjacent(pawn))
BINARY_INSERT_TARGET(reachable_targets, A, score)
// Go through our sorted target list until we find a path to one.
// Note: This does mean that the found target might not be the ideal one, as it's operating on the estimate
// This is a performance thing. We cannot actually use the true best target.
var/atom/ideal_atom
var/list/ideal_path
if(length(reachable_targets))
ideal_atom = reachable_targets[length(reachable_targets)]
else
while(length(targets_by_score))
var/atom/candidate = targets_by_score[length(targets_by_score)]
targets_by_score.len--
var/list/path = SSpathfinder.astar_pathfind_now(
controller.pawn,
candidate,
controller.max_target_distance,
required_distance,
access,
HAS_TRAIT(controller.pawn, TRAIT_FLYING),
)
if(path)
ideal_atom = candidate
ideal_path = path
break
if(set_path && length(ideal_path))
controller.clear_blackboard_key(BB_PATH_TO_USE)
controller.set_blackboard_key(BB_PATH_TO_USE, ideal_path)
return ideal_atom
#undef BINARY_INSERT_TARGET
/// Helper for scoring something based on the distance between it and the pawn.
/// By default, returns a value between 100 and -INFINITY, where 100 is a distance of 0 steps.
/// A distance equal to target_search_radius is zero.
/// A distance greater than target_search_radius is negative.
/datum/ai_behavior/proc/score_distance(datum/ai_controller/controller, atom/target)
var/search_radius = search_radius_override || controller.target_search_radius
if(isnull(target))
return -INFINITY
return 100 * (search_radius - get_dist_manhattan(get_turf(controller.pawn), get_turf(target))) / search_radius
+8 -2
View File
@@ -45,6 +45,8 @@ RESTRICT_TYPE(/datum/ai_controller)
var/continue_processing_when_client = FALSE
/// Distance to give up on target.
var/max_target_distance = 14
/// The search radius when looking for interesting things.
var/target_search_radius = 5
/// Cooldown for new plans, to prevent AI from going nuts if it can't think of new plans and looping on end
COOLDOWN_DECLARE(failed_planning_cooldown)
/// All subtrees this AI has available. Will run them in order, so make sure
@@ -142,8 +144,9 @@ RESTRICT_TYPE(/datum/ai_controller)
return
var/list/temp_subtree_list = list()
for(var/subtree in planning_subtrees)
var/subtree_instance = GLOB.ai_subtrees[subtree]
var/datum/ai_planning_subtree/subtree_instance = GLOB.ai_subtrees[subtree]
temp_subtree_list += subtree_instance
subtree_instance.setup(src)
planning_subtrees = temp_subtree_list
/// Proc to move from one pawn to another. This will destroy the target's existing controller.
@@ -344,7 +347,7 @@ RESTRICT_TYPE(/datum/ai_controller)
qdel(src)
/datum/ai_controller/proc/setup_able_to_run()
// paused_until is handled by PauseAi() manually
// paused_until is handled by pause_ai() manually
RegisterSignals(pawn, list(SIGNAL_ADDTRAIT(TRAIT_AI_PAUSED), SIGNAL_REMOVETRAIT(TRAIT_AI_PAUSED)), PROC_REF(update_able_to_run))
/datum/ai_controller/proc/clear_able_to_run()
@@ -521,6 +524,9 @@ RESTRICT_TYPE(/datum/ai_controller)
var/list/arguments = args.Copy()
arguments[1] = src
if(!behavior.setup(arglist(arguments)))
return FALSE
// It's still in the plan, don't add it again to current_behaviors
// but do keep it in the planned behavior list so its not cancelled
if(current_behaviors[behavior])
+6
View File
@@ -18,3 +18,9 @@
/// the other subtrees should multiple exist in the controller.
/datum/ai_planning_subtree/proc/select_behaviors(datum/ai_controller/controller, seconds_per_tick)
return
/datum/ai_planning_subtree/proc/setup(datum/ai_controller/controller)
return
/datum/ai_planning_subtree/proc/process_behavior_selection(datum/ai_controller/controller, seconds_per_tick)
select_behaviors(controller, seconds_per_tick)
@@ -0,0 +1,24 @@
/// A simple frustration behavior to be queued in addition to another behavior. If the given duration ellapses and the frustration key is still
/// present, the ai will cancel all of it's actions.
/datum/ai_behavior/frustration
action_cooldown = 0.1 SECONDS
/datum/ai_behavior/frustration/setup(datum/ai_controller/controller, frustration_key, duration)
. = ..()
controller.set_blackboard_key(frustration_key, world.time)
/datum/ai_behavior/frustration/perform(delta_time, datum/ai_controller/controller, frustration_key, duration)
..()
if(isnull(controller.blackboard[frustration_key]))
return AI_BEHAVIOR_SUCCEEDED
if(world.time >= duration + controller.blackboard[frustration_key])
controller.cancel_actions()
DEBUG_AI_LOG(controller, "AI got frustrated and cancelled current actions.")
return AI_BEHAVIOR_FAILED
return AI_BEHAVIOR_DELAY
/datum/ai_behavior/frustration/finish_action(datum/ai_controller/controller, succeeded, frustration_key, duration)
. = ..()
controller.clear_blackboard_key(frustration_key)
+47
View File
@@ -0,0 +1,47 @@
/datum/ai_planning_subtree/goap
var/list/possible_behaviors = list()
/datum/ai_planning_subtree/goap/setup(datum/ai_controller/controller, ...)
. = ..()
controller.set_blackboard_key(BB_PLANNER_BEHAVIORS, list())
for(var/behavior_type in possible_behaviors)
var/datum/ai_behavior/behavior = GET_AI_BEHAVIOR(behavior_type)
if(isnull(behavior))
stack_trace("Bad AI behavior: [behavior_type]")
continue
controller.set_blackboard_key_assoc(BB_PLANNER_BEHAVIORS, behavior, 0)
/datum/ai_planning_subtree/goap/select_behaviors(datum/ai_controller/controller, seconds_per_tick)
. = ..()
var/list/behavior_cache = controller.blackboard[BB_PLANNER_BEHAVIORS]
var/datum/ai_behavior/candidate = behavior_cache[1]
var/score_to_beat = behavior_cache[candidate]
for(var/behavior in behavior_cache)
var/behavior_score = behavior_cache[behavior]
if(behavior_score == AI_GOAP_SKIP_BEHAVIOR)
continue
// Filter out lower scoring behaviors
if(score_to_beat > behavior_score)
continue
// If there's a tie, pick either.
if((behavior_score == score_to_beat) && prob(50))
continue
candidate = behavior
score_to_beat = behavior_cache[candidate]
if(candidate)
controller.queue_behavior(candidate.type)
return SUBTREE_RETURN_FINISH_PLANNING
/datum/ai_planning_subtree/goap/process_behavior_selection(datum/ai_controller/controller, seconds_per_tick)
for(var/datum/ai_behavior/behavior in controller.blackboard[BB_PLANNER_BEHAVIORS])
if(behavior.goap_precondition(controller))
controller.set_blackboard_key_assoc(BB_PLANNER_BEHAVIORS, behavior, behavior.goap_weight * behavior.goap_score(controller))
else
controller.set_blackboard_key_assoc(BB_PLANNER_BEHAVIORS, behavior, AI_GOAP_SKIP_BEHAVIOR)
return ..()
@@ -0,0 +1,44 @@
/**
* This movement datum represents smart-pathing
*/
/datum/ai_movement/astar
max_pathing_attempts = 20
/// If FALSE, diagonals will be split into 2 cardinal moves.
var/use_diagonals = TRUE
/datum/ai_movement/astar/start_moving_towards(datum/ai_controller/controller, atom/current_movement_target, min_distance)
. = ..()
var/atom/movable/moving = controller.pawn
var/delay = controller.movement_delay
var/max_path_length_override = controller.blackboard[BB_PATH_MAX_LENGTH]
var/datum/move_loop/has_target/astar/loop = GLOB.move_manager.astar_move(
moving,
current_movement_target,
delay,
repath_delay = 0.5 SECONDS,
max_path_length = isnull(max_path_length_override) ? AI_MAX_PATH_LENGTH : max_path_length_override,
minimum_distance = controller.get_minimum_distance(),
access = controller.get_access(),
simulated_only = !HAS_TRAIT(controller.pawn, TRAIT_FLYING),
subsystem = SSai_movement,
extra_info = controller,
initial_path = controller.blackboard[BB_PATH_TO_USE],
use_diagonals = use_diagonals,
)
controller.clear_blackboard_key(BB_PATH_TO_USE)
RegisterSignal(loop, COMSIG_MOVELOOP_PREPROCESS_CHECK, PROC_REF(pre_move))
RegisterSignal(loop, COMSIG_MOVELOOP_POSTPROCESS, PROC_REF(post_move))
RegisterSignal(loop, COMSIG_MOVELOOP_ASTAR_REPATH, PROC_REF(repath_incoming))
return loop
/datum/ai_movement/astar/proc/repath_incoming(datum/move_loop/has_target/astar/source)
SIGNAL_HANDLER
var/datum/ai_controller/controller = source.extra_info
source.access = controller.get_access()
source.minimum_distance = controller.get_minimum_distance()
@@ -450,6 +450,16 @@
sheet_type = /obj/item/stack/sheet/mineral/iridium
ore_type = /obj/item/stack/ore/iridium
/datum/material/gnesis
name = "gnesis"
id = MAT_GNESIS
sheet_type = /obj/item/stack/sheet/gnesis
/datum/material/gnesis_glass
name = "translucent gnesis"
id = MAT_GNESIS_GLASS
sheet_type = /obj/item/stack/sheet/gnesis_glass
/datum/material/bananium
name = "Bananium"
id = MAT_BANANIUM
+64
View File
@@ -0,0 +1,64 @@
/datum/keybinding/flock/can_use(client/C, mob/M)
return isflockmob(M) && ..()
/datum/keybinding/flock/intent
/// The intent to switch to.
var/intent
/datum/keybinding/flock/intent/down(client/C)
. = ..()
var/mob/living/basic/flock/M = C.mob
M.a_intent_change(intent)
/datum/keybinding/flock/intent/help
name = "Help Intent (Tap)"
intent = INTENT_HELP
keys = list("1")
/datum/keybinding/flock/intent/disarm
name = "Disarm Intent (Tap)"
intent = INTENT_DISARM
keys = list("2")
/datum/keybinding/flock/intent/grab
name = "Grab Intent (Tap)"
intent = INTENT_GRAB
keys = list("3")
/datum/keybinding/flock/intent/harm
name = "Harm Intent (Tap)"
intent = INTENT_HARM
keys = list("4")
/datum/keybinding/flock/cycle_part
name = "Cycle Part"
keys = list("X")
/datum/keybinding/flock/cycle_part/can_use(client/C, mob/M)
return isflockdrone(M)
/datum/keybinding/flock/cycle_part/down(client/C)
. = ..()
var/mob/living/basic/flock/drone/M = C.mob
if(M.active_part == M.parts[1])
M.set_active_part(M.parts[2])
else if(M.active_part == M.parts[2])
M.set_active_part(M.parts[3])
else if(M.active_part == M.parts[3])
M.set_active_part(M.parts[1])
/datum/keybinding/flock/flockphase
name = "Toggle Flockphase"
keys = list("C")
/datum/keybinding/flock/flockphase/can_use(client/C, mob/M)
return isflockdrone(M)
/datum/keybinding/flock/flockphase/down(client/C)
. = ..()
var/mob/living/basic/flock/drone/M = C.mob
if(HAS_TRAIT(M, TRAIT_FLOCKPHASE))
M.stop_flockphase()
else
if(M.can_flockphase() && M.flockphase_tax())
M.start_flockphase()
@@ -6,6 +6,15 @@
end_sound = 'sound/machines/shower/shower_end.ogg'
volume = 20
/datum/looping_sound/flock_relay
mid_sounds = list('sound/goonstation/flockmind/Flock_Reactor.ogg' = 1)
mid_length = 33 SECONDS
volume = 40
extra_range = 10
falloff_exponent = 10
falloff_distance = 5
channel = CHANNEL_ENGINE
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/datum/looping_sound/supermatter
+72
View File
@@ -0,0 +1,72 @@
/// Completely generic resource handler
/datum/point_holder
VAR_PRIVATE/max_points = INFINITY
VAR_PRIVATE/points = 0
/datum/point_holder/proc/has_points(atleast)
if(points >= atleast)
return points
/datum/point_holder/proc/is_full()
return points == max_points
/// Returns the number of points needed to fill the holder.
/datum/point_holder/proc/how_empty()
return max_points - points
/// Returns the % (0-100) of how full the holder is.
/datum/point_holder/proc/percent(step = 1)
if(max_points == INFINITY)
return 0
return round((points / max_points) * 100, step)
/datum/point_holder/proc/adjust_points(num, check_enough)
if(num > 0)
return add_points(num)
else if(num < 0)
return remove_points(num, check_enough)
return 0
/// Add points. Returns the number of points added.
/datum/point_holder/proc/add_points(num)
if(points == max_points)
return 0
var/old = points
points = min(max_points, points + num)
return points - old
/// Removes points. Returns the number of points removed.
/datum/point_holder/proc/remove_points(num, check_enough)
if(check_enough && (points < num))
return 0
if(points == 0)
return 0
var/old = points
points = max(0, points - num)
return old - points
/datum/point_holder/proc/set_max_points(new_max)
max_points = new_max
points = min(max_points, points)
/datum/point_holder/proc/get_max_points()
return max_points
/datum/point_holder/infinite
/datum/point_holder/infinite/add_points(num)
return 0
/datum/point_holder/infinite/has_points(atleast)
return INFINITY
/datum/point_holder/infinite/remove_points(num, check_enough)
return 0
/datum/point_holder/infinite/set_max_points(new_max)
max_points = new_max
@@ -15,6 +15,10 @@
/// Can be used in certain situations where you may have effects that trigger only at the edge,
/// while also wanting the field effect to trigger at edge turfs as well
var/edge_is_a_field = FALSE
/// If TRUE, view() will be used over range(). If this is used, edge turfs will not exist!
var/use_view = FALSE
/// All turfs on the inside of the proximity monitor - range - 1 turfs
var/list/turf/field_turfs = list()
/// All turfs on the very last tile of the proximity monitor's radius
@@ -131,6 +135,13 @@
/datum/proximity_monitor/advanced/proc/update_new_turfs()
if(ignore_if_not_on_turf && !isturf(host.loc))
return list(FIELD_TURFS_KEY = list(), EDGE_TURFS_KEY = list())
if(use_view)
var/list/turfs = list()
for(var/turf/T as turf in view(current_range, host))
turfs += T
return list(FIELD_TURFS_KEY = turfs, EDGE_TURFS_KEY = list())
var/list/local_field_turfs = list()
var/list/local_edge_turfs = list()
var/turf/center = get_turf(host)
@@ -0,0 +1,14 @@
/datum/proximity_monitor/advanced/interceptor
use_view = TRUE
/datum/proximity_monitor/advanced/interceptor/New(atom/_host, range, _ignore_if_not_on_turf)
..()
recalculate_field(TRUE)
/datum/proximity_monitor/advanced/interceptor/Destroy()
return ..()
/datum/proximity_monitor/advanced/interceptor/field_turf_crossed(atom/movable/movable, turf/old_location, turf/new_location)
. = ..()
if(isprojectile(movable))
astype(host, /obj/structure/flock/interceptor).try_intercept_projectile(movable)
+7
View File
@@ -98,6 +98,13 @@
display_alt_appearance(key, displayTo)
/*
Gets an alternate appearance for the provided key
key - the key to the assoc list of key = /datum/alternate_appearances
*/
/atom/proc/get_alt_appearance(key)
return alternate_appearances?[key]
//////////////
// WRAPPERS //
//////////////
+1
View File
@@ -19,6 +19,7 @@ GLOBAL_LIST_INIT(role_playtime_requirements, list(
ROLE_MORPH = 5,
ROLE_DEMON = 5,
ROLE_ELITE = 5,
ROLE_FLOCK = 20,
// DUO ANTAGS
ROLE_GUARDIAN = 20,
@@ -68,3 +68,9 @@
/obj/effect/decal/cleanable/blood/gibs/down/vox
basecolor = "#2299FC"
fleshcolor = "#808D11"
/obj/effect/decal/cleanable/blood/gibs/flock
basecolor = COLOR_BLOOD_FLOCK
/obj/effect/decal/cleanable/blood/gibs/core/flock
basecolor = COLOR_BLOOD_FLOCK
+3
View File
@@ -7,6 +7,9 @@
/proc/xgibs(atom/location)
new /obj/effect/gibspawner/xeno(get_turf(location))
/proc/flockgibs(atom/location)
new /obj/effect/gibspawner/flock(get_turf(location))
/proc/robogibs(atom/location)
new /obj/effect/gibspawner/robot(get_turf(location))
+63
View File
@@ -0,0 +1,63 @@
/obj/effect/abstract/info_tag
plane = INFO_TAG_PLANE
layer = 1
alpha = 180
appearance_flags = APPEARANCE_UI_IGNORE_ALPHA
vis_flags = VIS_INHERIT_ID
/// Atom we're displaying over.
var/atom/parent
/// Mobs that see us rn.
var/list/mob/viewing_mobs
/// Images we're inserting into client.images
VAR_PRIVATE/image/vis_holder
/obj/effect/abstract/info_tag/Initialize(mapload)
. = ..()
vis_holder = new()
vis_holder.plane = INFO_TAG_PLANE
vis_holder.vis_contents += src
/obj/effect/abstract/info_tag/Destroy(force)
set_parent(null)
vis_holder = null
for(var/mob/M in viewing_mobs)
hide_from(M.client)
viewing_mobs = null
return ..()
/obj/effect/abstract/info_tag/proc/set_parent(atom/new_parent)
parent = new_parent
vis_holder.loc = parent
/// Update the text.
/obj/effect/abstract/info_tag/proc/set_text(text)
maptext = "<span class='maptext' style='text-align: center;font-size: 6px'>[text]</span>"
/// Should the given mob be able to see this tag?
/obj/effect/abstract/info_tag/proc/mob_should_see(mob/M)
return TRUE
/obj/effect/abstract/info_tag/proc/show_to(client/C)
C?.images |= vis_holder
/obj/effect/abstract/info_tag/proc/hide_from(client/C)
C?.images -= vis_holder
/obj/effect/abstract/info_tag/flock
maptext_x = -64
maptext_y = -6
maptext_width = 160
maptext_height = 48
/obj/effect/abstract/info_tag/flock/mob_should_see(mob/M)
return (M != parent) && isflockmob(M)
/obj/effect/abstract/info_tag/flock/info
maptext_y = -14
+4
View File
@@ -104,6 +104,10 @@ INITIALIZE_IMMEDIATE(/obj/effect/landmark/newplayer_start) //Without this you sp
name = "revenantspawn"
icon_state = "Rev"
/obj/effect/landmark/spawner/flock
name = "flockspawn"
icon_state = "flockmind"
/obj/effect/landmark/spawner/ninja
name = "ninjaspawn"
@@ -54,3 +54,15 @@
/obj/effect/gibspawner/vox/gib_dna(obj/effect/decal/cleanable/blood/gibs/gib, datum/dna/mob_dna)
if(!..()) // Probably admin spawned
gib.blood_DNA["Unknown Vox DNA"] = "A+"
/obj/effect/gibspawner/flock
gibtypes = list(/obj/effect/decal/cleanable/blood/gibs/flock, /obj/effect/decal/cleanable/blood/gibs/flock, /obj/effect/decal/cleanable/blood/gibs/core/flock)
gibamounts = list(2,2,1)
/obj/effect/gibspawner/flock/New()
gibdirections = list(list(WEST, NORTHWEST, SOUTHWEST, NORTH), list(EAST, NORTHEAST, SOUTHEAST, SOUTH), list())
..()
/obj/effect/gibspawner/flock/gib_dna(obj/effect/decal/cleanable/blood/gibs/gib, datum/dna/mob_dna)
if(!..())
gib.blood_DNA["UNKNOWN DNA"] = "&#^*@!"
@@ -5,6 +5,7 @@
* 4. REINFORCED PLASMA GLASS
* 5. TITANIUM GLASS
* 6. PLASTITANIUM GLASS
* 7. GNESIS GLASS
Todo: Create a unified construct_window(sheet, user, created_window, full_window)
@@ -26,6 +27,13 @@ GLOBAL_LIST_INIT(glass_recipes, list (
new /datum/stack_recipe("dropper", /obj/item/reagent_containers/dropper, 1, time = 1 SECONDS),
))
GLOBAL_LIST_INIT(gnesis_glass_recipes, list (
new /datum/stack_recipe("bright shard", /obj/item/shard/gnesis_glass, time = 0 SECONDS),
new /datum/stack_recipe/window("directional gnesis window", /obj/structure/window/flock, time = 1 SECONDS, on_floor = TRUE, window_checks = TRUE),
new /datum/stack_recipe/window("fulltile gnesis window", /obj/structure/window/flock/fulltile, 2, time = 2 SECONDS, on_floor = TRUE, window_checks = TRUE),
new /datum/stack_recipe("gnesis cache", /obj/item/reagent_containers/glass/gnesis, 5, time = 2 SECONDS),
))
/obj/item/stack/sheet/glass
name = "glass"
desc = "HOLY SHEET! That is a lot of glass."
@@ -312,3 +320,21 @@ GLOBAL_LIST_INIT(plastitaniumglass_recipes, list(
. = ..()
recipes = GLOB.plastitaniumglass_recipes
//////////////////////////////
// MARK: GNESIS GLASS
//////////////////////////////
/obj/item/stack/sheet/gnesis_glass
name = "transculent wafers"
singular_name = "transculent wafer"
desc = "A rare, complex crystalline matrix with a lazily shifting internal structure. The layers are arranged to let light through."
icon_state = "gnesisglass"
materials = list(MAT_GNESIS_GLASS = MINERAL_MATERIAL_AMOUNT)
merge_type = /obj/item/stack/sheet/gnesis_glass
armor = list(MELEE = 20, BULLET = 20, LASER = 0, ENERGY = 100, BOMB = 0, RAD = 50, FIRE = 100, ACID = 20)
resistance_flags = FIRE_PROOF
point_value = 5
dynamic_icon_state = FALSE
/obj/item/stack/sheet/gnesis_glass/Initialize(mapload, new_amount, merge)
. = ..()
recipes = GLOB.gnesis_glass_recipes
@@ -13,6 +13,7 @@ Mineral Sheets
- Platinum
- Alien Alloy
- Adamantine
- Gnesis
*/
GLOBAL_LIST_INIT(sandstone_recipes, list (
@@ -138,6 +139,13 @@ GLOBAL_LIST_INIT(adamantine_recipes, list(
new /datum/stack_recipe("incomplete servant golem shell", /obj/item/golem_shell/servant, req_amount = 1, res_amount = 1),
))
GLOBAL_LIST_INIT(gnesis_recipes, list (
new /datum/stack_recipe("flock chair", /obj/structure/chair/comfy/flock, 2, time = 1 SECONDS, one_per_turf = TRUE, on_floor = TRUE),
new /datum/stack_recipe("flock closet", /obj/structure/closet/flock, 2, time = 10 SECONDS, one_per_turf = TRUE, on_floor = TRUE),
new /datum/stack_recipe("flock grille", /obj/structure/grille/flock, 1, time = 5 SECONDS, one_per_turf = TRUE, on_floor = TRUE),
new /datum/stack_recipe("flock door", /obj/machinery/door/flock, 10, time = 10 SECONDS, one_per_turf = TRUE, on_floor = TRUE),
))
GLOBAL_LIST_INIT(snow_recipes, list(
new /datum/stack_recipe("snowman", /obj/structure/snowman, 5, time = 2.5 SECONDS, one_per_turf = TRUE, on_floor = TRUE),
new /datum/stack_recipe("Snowball", /obj/item/snowball, 1)
@@ -600,6 +608,23 @@ GLOBAL_LIST_INIT(plastitanium_recipes, list(
/obj/item/stack/sheet/mineral/adamantine/fifty
amount = 50
/*
* Gnesis
*/
/obj/item/stack/sheet/gnesis
name = "wafers"
desc = "A rare, complex crystalline matrix with a lazily shifting internal structure. Not to be confused with gneiss, a metamorphic rock."
singular_name = "wafer"
icon_state = "gnesis"
materials = list(MAT_GNESIS = MINERAL_MATERIAL_AMOUNT)
merge_type = /obj/item/stack/sheet/gnesis
dynamic_icon_state = FALSE
/obj/item/stack/sheet/gnesis/Initialize(mapload, new_amount, merge)
. = ..()
recipes = GLOB.gnesis_recipes
/*
* Snow
*/
@@ -105,3 +105,12 @@
materials = list(MAT_TITANIUM = MINERAL_MATERIAL_AMOUNT * 0.5, MAT_PLASMA = MINERAL_MATERIAL_AMOUNT * 0.5, MAT_GLASS = MINERAL_MATERIAL_AMOUNT)
icon_prefix = "plastitanium"
welded_type = /obj/item/stack/sheet/plastitaniumglass
/obj/item/shard/gnesis_glass
name = "bright shard"
desc = "A jagged shard of weird alien computer crystal stuff."
color = "#1bdebd"
force = 7
throwforce = 12
materials = list(MAT_GNESIS_GLASS = MINERAL_MATERIAL_AMOUNT * 0.5)
welded_type = /obj/item/stack/sheet/gnesis_glass
+9
View File
@@ -19,6 +19,15 @@
if(obj_integrity <= 0)
obj_destruction(damage_flag)
/**
* Retrieves the objects's current damage as a percentage where `100%` is `100`.
*/
/obj/proc/get_integrity_percentage()
return ceil(obj_integrity / max_integrity * 100)
/obj/proc/get_integrity_lost()
return max_integrity - obj_integrity
///returns the damage value of the attack after processing the obj's various armor protections
/obj/proc/run_obj_armor(damage_amount, damage_type, damage_flag = 0, attack_dir, armor_penetration_flat = 0, armor_penetration_percentage = 0)
if(damage_flag == MELEE && damage_amount < damage_deflection)
+7
View File
@@ -107,3 +107,10 @@
sheet_type = /obj/item/stack/sheet/runed_metal
sheet_amount = 1
girder_type = /obj/structure/girder/cult
/turf/simulated/wall/gnesis
name = "gnesis wall"
desc = "A thick metal wall, it look very old."
icon = 'icons/goonstation/turf/flock.dmi'
color = "#1bdebd"
sheet_type = /obj/item/stack/sheet/gnesis
@@ -0,0 +1,42 @@
/datum/action/cooldown/flock/cage_mob
name = "Cage"
cooldown_time = 5 SECONDS
click_to_activate = TRUE
/datum/action/cooldown/flock/cage_mob/is_valid_target(atom/cast_on)
return isliving(cast_on) && !isflockmob(cast_on) && isturf(cast_on.loc)
/datum/action/cooldown/flock/cage_mob/Activate(atom/target)
if(DOING_INTERACTION(owner, "flock_cage"))
return FALSE
var/mob/living/basic/flock/drone/bird = owner
bird.stop_flockphase(TRUE)
var/turf/T = get_turf(target)
owner.visible_message(
SPAN_NOTICE("<b>[owner]</b> begins forming a cuboid structure around <b>[target]</b>."),
blind_message = SPAN_HEAR("You hear a strange synthetic whirring."),
)
var/obj/effect/temp_visual/flock_effect
if(iswallturf(T))
flock_effect = new /obj/effect/temp_visual/flock/wall_convert(T)
log_attack(owner, target, "attempted to cage")
. = TRUE
playsound(owner, 'sound/goonstation/flockmind/flockdrone_build.ogg', 30, TRUE, extrarange = SILENCED_SOUND_EXTRARANGE)
if(!do_after(owner, 4.5 SECONDS, target = target, interaction_key = "flock_cage"))
. = FALSE
qdel(flock_effect)
if(!.)
return
log_attack(owner, target, "caged")
playsound(owner, 'sound/goonstation/flockmind/flockdrone_build_complete.ogg', 70, TRUE)
var/obj/structure/flock/cage/cage = new(T, bird.flock)
cage.cage_mob(target)
..()
@@ -0,0 +1,148 @@
/datum/action/cooldown/flock/control_drone
name = "Control Drone"
desc = "Direct a drone to perform an action."
button_icon_state = "ping"
click_to_activate = TRUE
unset_after_click = FALSE
click_cd_override = 0
var/mob/living/basic/flock/drone/selected_bird
/datum/action/cooldown/flock/control_drone/Remove(mob/removed_from)
. = ..()
free_drone()
/datum/action/cooldown/flock/control_drone/is_valid_target(atom/cast_on)
. = ..()
if(selected_bird)
return get_turf(cast_on)
if(!isflockdrone(cast_on))
return FALSE
var/mob/living/basic/flock/drone/bird = cast_on
if(HAS_TRAIT_FROM(bird, TRAIT_AI_DISABLE_PLANNING, FLOCK_CONTROLLED_BY_OVERMIND_SOURCE))
return FALSE
if(bird.controlled_by)
return FALSE
return TRUE
/datum/action/cooldown/flock/control_drone/Activate(atom/target)
. = ..()
if(isnull(selected_bird))
bind_drone(target)
return TRUE
selected_bird.ai_controller.cancel_actions()
// Move to turf/structure, or convert turf.
if(isturf(target) || istype(target, /obj/structure/flock))
if(istype(target, /obj/structure/flock/tealprint))
if(!selected_bird.ai_controller.queue_behavior(/datum/ai_behavior/flock/find_deposit_target, target))
return FALSE
pointer_helper(selected_bird, target, 2 SECONDS)
unset_click_ability(owner, performing_task = TRUE)
return TRUE
var/turf/T = get_turf(target)
if(isflockturf(T))
if(!selected_bird.ai_controller.queue_behavior(/datum/ai_behavior/flock/rally, target))
return FALSE
pointer_helper(selected_bird, target, 2 SECONDS)
unset_click_ability(owner, performing_task = TRUE)
return TRUE
if(T.can_flock_convert())
if(!selected_bird.ai_controller.queue_behavior(/datum/ai_behavior/flock/find_conversion_target, target))
return FALSE
pointer_helper(selected_bird, target, 2 SECONDS)
unset_click_ability(owner, performing_task = TRUE)
return TRUE
// Harvest items
else if(isitem(target))
if(!selected_bird.ai_controller.queue_behavior(/datum/ai_behavior/flock/find_harvest_target, target))
return FALSE
pointer_helper(selected_bird, target, 2 SECONDS)
unset_click_ability(owner, performing_task = TRUE)
return TRUE
// Attack or convert enemies.
else if(ismob(target) && selected_bird.flock.is_mob_enemy(target))
var/mob/living/L = target
if(L.incapacitated(ignore_restraints = TRUE, ignore_grab = TRUE) || L.IsKnockedDown())
if(!selected_bird.ai_controller.queue_behavior(/datum/ai_behavior/flock/find_capture_target, target))
return FALSE
pointer_helper(selected_bird, target, 2 SECONDS)
unset_click_ability(owner, performing_task = TRUE)
return TRUE
else if(selected_bird.ai_controller.queue_behavior(/datum/ai_behavior/flock/attack_target, target))
pointer_helper(selected_bird, target, 2 SECONDS)
unset_click_ability(owner, performing_task = TRUE)
return TRUE
return FALSE
else if(isflockmob(target))
if(istype(target, /mob/living/basic/flock/bit))
to_chat(owner, SPAN_WARNING("Flockbits are too simple to be remotely controlled."))
return FALSE
var/mob/living/basic/flock/other_bird = target
if(other_bird.flock == selected_bird.flock)
if(!selected_bird.ai_controller.queue_behavior(/datum/ai_behavior/flock/find_heal_target, target))
return FALSE
pointer_helper(selected_bird, target, 2 SECONDS)
unset_click_ability(owner, performing_task = TRUE)
return TRUE
/datum/action/cooldown/flock/control_drone/unset_click_ability(mob/on_who, refund_cooldown, performing_task = TRUE)
. = ..()
free_drone(performing_task)
/datum/action/cooldown/flock/control_drone/proc/pointer_helper(atom/from, atom/towards, duration)
var/mob/camera/flock/overmind/ghost_bird = owner
var/image/pointer = pointer_image_to(from, towards)
animate(pointer, time = 2 SECONDS, alpha = 0)
ghost_bird.flock.add_ping_image(ghost_bird.client, pointer, 2 SECONDS)
/datum/action/cooldown/flock/control_drone/proc/bind_drone(mob/living/basic/flock/drone/bird)
selected_bird = bird
RegisterSignal(bird, COMSIG_PARENT_QDELETING, PROC_REF(drone_gone))
ADD_TRAIT(bird, TRAIT_AI_DISABLE_PLANNING, FLOCK_CONTROLLED_BY_OVERMIND_SOURCE)
bird.ai_controller.cancel_actions()
bird.say("suspending automated subroutines pending sentient-level instruction", forced = TRUE)
bird.AddComponent(/datum/component/flock_ping/selected)
/datum/action/cooldown/flock/control_drone/proc/free_drone(performing_task = TRUE)
if(!selected_bird)
return
if(!QDELETED(selected_bird) && !performing_task)
spawn(-1)
selected_bird.say("Sentient-level instruction suspended, resuming automated subroutines.", forced = TRUE)
UnregisterSignal(selected_bird, COMSIG_PARENT_QDELETING)
REMOVE_TRAIT(selected_bird, TRAIT_AI_DISABLE_PLANNING, FLOCK_CONTROLLED_BY_OVERMIND_SOURCE)
qdel(selected_bird.GetComponent(/datum/component/flock_ping/selected))
selected_bird = null
unset_click_ability(owner)
/datum/action/cooldown/flock/control_drone/proc/drone_gone(datum/source)
SIGNAL_HANDLER
free_drone()
@@ -0,0 +1,27 @@
/datum/action/cooldown/flock/create_rift
name = "Spawn Rift"
desc = "Spawn an rift where you are, and from there, begin."
button_icon_state = "spawn_egg"
/datum/action/cooldown/flock/create_rift/is_valid_target(atom/cast_on)
var/turf/T = get_turf(owner)
if(!T.is_safe())
to_chat(owner, SPAN_WARNING("This place is not safe enough for a rift."))
return FALSE
if(T.is_blocked_turf())
to_chat(owner, SPAN_WARNING("There is not enough room for a rift here."))
return FALSE
return TRUE
/datum/action/cooldown/flock/create_rift/Activate(atom/target)
. = ..()
var/turf/T = get_turf(owner)
if(tgui_alert(owner, "Would you like to create the rift on [T]?", "Spawn Rift", list("Yes", "No")) != "Yes")
return
var/mob/camera/flock/overmind/ghost_bird = owner
ghost_bird.spawn_rift(T)
@@ -0,0 +1,49 @@
/datum/action/cooldown/flock/create_structure
name = "Place Tealprint"
desc = "Create a structure tealprint for your drones to construct onto."
button_icon_state = "fabstructure"
/datum/action/cooldown/flock/create_structure/is_valid_target(atom/cast_on)
var/turf/simulated/floor/flock/T = get_turf(owner)
if(!istype(T))
to_chat(owner, SPAN_WARNING("We are unable to perform complex substrate manipulation outside of our tiles."))
return FALSE
if(T.broken)
to_chat(owner, SPAN_WARNING("The tile we are above is broken."))
return FALSE
if(locate(/obj/structure/flock/tealprint) in T)
to_chat(owner, SPAN_WARNING("We are currently building something there."))
return FALSE
if(locate(/obj/structure/flock) in T)
to_chat(owner, SPAN_WARNING("An existing structure is occupying that tile."))
return FALSE
return TRUE
/datum/action/cooldown/flock/create_structure/Activate(atom/target)
var/mob/camera/flock/overmind/ghost_bird = owner
var/datum/flock/F = ghost_bird.flock
var/turf/loc = get_turf(ghost_bird)
var/options = list()
for(var/datum/flock_unlockable/unlockable as anything in F.unlockables)
if(!unlockable.unlocked)
continue
options[unlockable.name] = unlockable
var/datum/flock_unlockable/desired = tgui_input_list(ghost_bird, "Select a structure to create. Bandwidth upkeep costs are provided in parenthesis.", "Tealprint Selection", options)
desired = options[desired]
if(isnull(desired))
return
F.refresh_unlockables()
if(!desired.unlocked)
return
F.create_structure(loc, desired.structure_type)
return ..()
@@ -0,0 +1,43 @@
/datum/action/cooldown/flock/deconstruct
name = "Deconstruct"
click_to_activate = TRUE
check_flags = AB_CHECK_CONSCIOUS
/datum/action/cooldown/flock/deconstruct/is_valid_target(atom/cast_on)
var/turf/clicked_atom_turf = get_turf(cast_on)
if(!clicked_atom_turf)
return FALSE
if(get_dist(owner, clicked_atom_turf) > 1)
return FALSE
if(ismob(cast_on) || !HAS_TRAIT(cast_on, TRAIT_FLOCK_THING) || HAS_TRAIT(cast_on, TRAIT_FLOCK_NODECON))
return FALSE
return TRUE
/datum/action/cooldown/flock/deconstruct/Activate(atom/target)
. = ..()
var/mob/living/basic/flock/bird = owner
astype(bird, /mob/living/basic/flock/drone)?.stop_flockphase(TRUE)
bird.face_atom(target)
bird.visible_message(SPAN_NOTICE("[bird] begins to deconstruct [target]."), blind_message = SPAN_HEAR("You hear an otherwordly whirring."))
if(!do_after(bird, 6 SECONDS, target = target, interaction_key = "flock_deconstruct"))
return FALSE
if(!is_valid_target(target))
return FALSE
// Structures
if(isobj(target))
var/obj/obj_target = target
obj_target.deconstruct(TRUE)
return TRUE
else if(isflockturf(target))
var/turf/turf = target
turf.ChangeTurf(/turf/simulated/floor/flock, ignore_air = TRUE)
else
CRASH("Tried to flock deconstruct incompatible object of type: [target.type]")
return TRUE
@@ -0,0 +1,23 @@
/datum/action/cooldown/flock/deposit
name = "Deposit"
click_to_activate = TRUE
check_flags = AB_CHECK_CONSCIOUS
/datum/action/cooldown/flock/deposit/is_valid_target(atom/cast_on)
return istype(cast_on, /obj/structure/flock/tealprint)
/datum/action/cooldown/flock/deposit/Activate(atom/target)
. = ..()
var/mob/living/basic/flock/bird = owner
astype(bird, /mob/living/basic/flock/drone)?.stop_flockphase(TRUE)
bird.face_atom(target)
playsound(target, 'sound/goonstation/flockmind/flockdrone_quickbuild.ogg', 50, TRUE)
while(do_after(bird, 1 SECONDS, target = target, interaction_key = "flock_deposit"))
var/obj/structure/flock/tealprint/tealprint = target
var/deposit = min(bird.substrate.has_points(), tealprint.substrate.how_empty(), FLOCK_SUBSTRATE_COST_DEPOST_TEALPRINT)
bird.substrate.remove_points(deposit)
tealprint.substrate.add_points(deposit)
return TRUE
@@ -0,0 +1,18 @@
/datum/action/cooldown/flock/designate_deconstruct
name = "Mark for Deconstruction"
desc = "Mark an existing flock structure for deconstruction, refunding some resources."
button_icon_state = "destroystructure"
click_to_activate = TRUE
unset_after_click = FALSE
click_cd_override = 0
/datum/action/cooldown/flock/designate_deconstruct/Activate(atom/target)
. = ..()
if(istype(target, /obj/structure/flock/tealprint))
var/obj/structure/flock/tealprint/tealprint = target
return tealprint.try_cancel_structure()
var/mob/camera/flock/ghost_bird = owner
return ghost_bird.flock.toggle_deconstruction_mark(target)
@@ -0,0 +1,65 @@
/datum/action/cooldown/flock/designate_enemy
name = "Designate Enemy"
desc = "Mark or unmark someone as an enemy."
button_icon_state = "designate_enemy"
click_to_activate = TRUE
unset_after_click = FALSE
click_cd_override = 0
/datum/action/cooldown/flock/designate_enemy/is_valid_target(atom/cast_on)
var/atom/movable/target_movable = cast_on
if(!istype(target_movable))
return FALSE
return !isflockmob(cast_on) && isliving(cast_on) || target_movable.has_buckled_mobs()
/datum/action/cooldown/flock/designate_enemy/Activate(atom/target)
var/mob/camera/flock/ghost_bird = owner
var/datum/flock/flock = ghost_bird.flock
if(!flock)
return FALSE
. = ..()
if(flock.is_mob_ignored(target))
flock.remove_ignore(target)
else if(flock.is_mob_enemy(target))
flock.remove_enemy(target)
return TRUE
flock.update_enemy(target)
return TRUE
/datum/action/cooldown/flock/designate_ignore
name = "Designate Ignore"
desc = "Designate someone to be ignored by your Flock."
button_icon_state = "designate_ignore"
click_to_activate = TRUE
unset_after_click = FALSE
/datum/action/cooldown/flock/designate_ignore/is_valid_target(atom/cast_on)
var/atom/movable/target_movable = cast_on
if(!istype(target_movable))
return FALSE
return !isflockmob(cast_on) && isliving(cast_on) || target_movable.has_buckled_mobs()
/datum/action/cooldown/flock/designate_ignore/Activate(atom/target)
var/mob/camera/flock/ghost_bird = owner
var/datum/flock/flock = ghost_bird.flock
if(!flock)
return FALSE
. = ..()
if(flock.is_mob_ignored(target))
flock.remove_ignore(target)
else if(flock.is_mob_enemy(target))
flock.remove_enemy(target)
return TRUE
flock.add_ignore(target)
return TRUE
@@ -0,0 +1,37 @@
/datum/action/cooldown/flock/designate_tile
name = "Designate Priority Tile"
desc = "Add or remove a tile to the urgent tiles the Flock should claim."
button_icon_state = "designate_tile"
click_to_activate = TRUE
unset_after_click = FALSE
click_cd_override = 0
/datum/action/cooldown/flock/designate_tile/is_valid_target(atom/cast_on)
var/turf/T = get_turf(cast_on)
return T?.can_flock_convert()
/datum/action/cooldown/flock/designate_tile/Activate(atom/target)
var/turf/turf_target = get_turf(target)
if(isflockturf(target))
to_chat(owner, SPAN_ALERT("That tile has already been converted by the flock."))
return FALSE
if(!turf_target.can_flock_convert())
to_chat(owner, SPAN_ALERT("The flock is unable to convert that."))
return FALSE
var/mob/camera/flock/ghost_bird = owner
if(!ghost_bird.flock.marked_for_conversion[turf_target])
if(ghost_bird.flock.turf_reservations[turf_target])
to_chat(ghost_bird, SPAN_ALERT("That tile is already scheduled for conversion."))
return FALSE
ghost_bird.flock.marked_for_conversion[turf_target] = TRUE
ghost_bird.flock.add_notice(turf_target, FLOCK_NOTICE_PRIORITY)
return TRUE
else
ghost_bird.flock.marked_for_conversion -= turf_target
ghost_bird.flock.remove_notice(turf_target, FLOCK_NOTICE_PRIORITY)
return TRUE
@@ -0,0 +1,23 @@
/datum/action/cooldown/flock/diffract_drone
name = "Diffract Drone"
desc = "Split a drone into flockbits, mindless automata that only convert whatever they find."
button_icon_state = "diffract"
click_to_activate = TRUE
/datum/action/cooldown/flock/diffract_drone/is_valid_target(atom/cast_on)
if(!isflockdrone(cast_on))
return FALSE
var/mob/living/basic/flock/drone/bird = cast_on
if(bird.stat == DEAD)
return FALSE
return TRUE
/datum/action/cooldown/flock/diffract_drone/PreActivate(atom/target)
. = ..()
/datum/action/cooldown/flock/diffract_drone/Activate(atom/target)
. = ..()
var/mob/living/basic/flock/drone/bird = target
bird.split_into_bits()
@@ -0,0 +1,3 @@
/datum/action/cooldown/flock
button_icon = 'icons/goonstation/hud/flock_ui.dmi'
background_icon = null
@@ -0,0 +1,9 @@
/datum/action/cooldown/flock/control_panel
name = "Flock Control Panel"
desc = "Open the Flock control panel."
button_icon_state = "radio_stun"
/datum/action/cooldown/flock/control_panel/Activate(atom/target)
. = ..()
var/mob/camera/flock/ghost_bird = owner
ghost_bird.flock.ui_interact(ghost_bird)
@@ -0,0 +1,67 @@
/datum/action/cooldown/flock/convert
name = "Convert"
click_to_activate = TRUE
check_flags = AB_CHECK_CONSCIOUS
/datum/action/cooldown/flock/convert/New(Target)
. = ..()
/datum/action/cooldown/flock/convert/Destroy()
return ..()
/datum/action/cooldown/flock/convert/is_valid_target(atom/cast_on)
var/turf/clicked_atom_turf = get_turf(cast_on)
if(!clicked_atom_turf)
return FALSE
// This breaks pathfinding :(
// if(!owner.CanReach(clicked_atom_turf))
// return FALSE
// So instead:
if(get_dist(owner, clicked_atom_turf) > 1)
return FALSE
if(!clicked_atom_turf.can_flock_convert())
return FALSE
return TRUE
/datum/action/cooldown/flock/convert/Activate(atom/target)
. = ..()
playsound(owner, 'sound/goonstation/flockmind/flockdrone_convert.ogg', 30, TRUE, extrarange = SILENCED_SOUND_EXTRARANGE)
var/turf/clicked_atom_turf = get_turf(target)
if(isfloorturf(clicked_atom_turf) || iswallturf(clicked_atom_turf))
return convert_turf(clicked_atom_turf)
return FALSE
/datum/action/cooldown/flock/convert/proc/convert_turf(turf/T)
if(!is_valid_target(T))
return FALSE
var/mob/living/basic/flock/bird = owner
var/obj/effect/temp_visual/flock_effect
if(iswallturf(T))
flock_effect = new /obj/effect/temp_visual/flock/wall_convert(T)
else
flock_effect = new /obj/effect/temp_visual/flock/floor_convert(T)
owner.face_atom(T)
bird.flock?.reserve_turf(bird, T)
if(!do_after(owner, 4.5 SECONDS, target = T, interaction_key = "flock_convert"))
qdel(flock_effect)
bird.flock?.free_turf(bird)
return FALSE
bird.flock?.free_turf(bird)
qdel(flock_effect)
bird.flock?.claim_turf(T)
return TRUE
@@ -0,0 +1,24 @@
/datum/action/cooldown/flock/flock_heal
name = "Repair"
click_to_activate = TRUE
/datum/action/cooldown/flock/flock_heal/Activate(atom/target)
var/mob/living/basic/flock/drone/this_bird = owner
if(!this_bird.substrate.has_points(FLOCK_SUBSTRATE_COST_REPAIR))
return FALSE
astype(this_bird, /mob/living/basic/flock/drone)?.stop_flockphase(TRUE)
if(isflockmob(target))
var/mob/living/basic/flock/bird = target
ADD_TRAIT(bird, TRAIT_AI_PAUSED, ref(src))
if(!do_after(owner, 1 SECONDS, target = target, interaction_key = "flock_repair"))
REMOVE_TRAIT(bird, TRAIT_AI_PAUSED, ref(src))
return FALSE
REMOVE_TRAIT(bird, TRAIT_AI_PAUSED, ref(src))
bird.heal_overall_damage(10, 10)
this_bird.substrate.remove_points(FLOCK_SUBSTRATE_COST_REPAIR)
..()
return TRUE
@@ -0,0 +1,30 @@
/datum/action/cooldown/flock/repair_burst
name = "Concentrated Repair Burst"
desc = "Transmit large amounts of energy into up to four drones in a small area, healing them up to half of their hitpoints. Does not work on dead drones."
button_icon_state = "heal_drone"
cooldown_time = 30 SECONDS
click_to_activate = TRUE
/datum/action/cooldown/flock/repair_burst/Activate(atom/target)
var/list/targets = list()
for(var/mob/living/basic/flock/drone/bird in range(3, get_turf(target)))
if(bird.stat == DEAD)
continue
targets += bird
if(length(targets) >= 4)
break
if(!length(targets))
to_chat(owner, SPAN_ALERT("No repairable drones in range."))
return FALSE
. = ..()
to_chat(owner, SPAN_NOTICE("You transmit energy into your drones!"))
playsound(owner, 'sound/goonstation/flockmind/flockmind_cast.ogg', 50)
for(var/mob/living/basic/flock/drone/bird as anything in targets)
playsound(bird, "sound/effects/radio_sweep[rand(1,5)].ogg", 50, TRUE, SHORT_RANGE_SOUND_EXTRARANGE)
new /obj/effect/temp_visual/flock/flock_heal_burst(get_turf(bird))
bird.adjustHealth(-50)
@@ -0,0 +1,44 @@
/datum/action/cooldown/flock/nest
name = "Lay Egg"
button_icon_state = "spawn_egg"
check_flags = AB_CHECK_CONSCIOUS
/datum/action/cooldown/flock/nest/is_valid_target(atom/cast_on)
var/mob/living/basic/flock/drone/bird = owner
var/turf/simulated/floor/flock/flockfloor = get_turf(cast_on)
if(!istype(flockfloor) || flockfloor.is_blocked_turf(source_atom = owner))
to_chat(owner, SPAN_WARNING("The egg must be placed on an open flock tile."))
return FALSE
if(!bird.flock)
to_chat(owner, SPAN_WARNING("Our prime directives prohibit us from synthesizing an egg."))
return FALSE
if(!bird.substrate.has_points(bird.flock.current_egg_cost))
to_chat(owner, SPAN_WARNING("Error: Not enough resources. (We need [bird.flock.current_egg_cost])"))
return FALSE
if(HAS_TRAIT(bird, TRAIT_FLOCKPHASE))
to_chat(owner, SPAN_WARNING("We can not synthesize eggs while flockrunning."))
return FALSE
return TRUE
/datum/action/cooldown/flock/nest/Activate(atom/target)
. = ..()
var/mob/living/basic/flock/drone/bird = owner
if(!bird.substrate.has_points(bird.flock?.current_egg_cost || FLOCK_SUBSTRATE_COST_LAY_EGG))
return FALSE
var/turf/simulated/floor/flock/flockfloor = get_turf(target)
bird.stop_flockphase(TRUE)
to_chat(bird, SPAN_NOTICE("Our internal fabricators spring into action, we must hold still."))
if(!do_after_once(bird, 8 SECONDS, needhand = FALSE, target = bird.loc, interaction_key = "flock_lay_egg"))
return FALSE
bird.substrate.remove_points(bird.flock.current_egg_cost)
bird.visible_message(SPAN_NOTICE("[bird] deploys some sort of device."))
new /obj/structure/flock/egg(flockfloor)
return TRUE
@@ -0,0 +1,14 @@
/datum/action/cooldown/flock/ping
name = "Ping"
desc = "Request attention from other partitions of the Flock."
button_icon_state = "ping"
cooldown_time = 0.3 SECONDS
click_to_activate = TRUE
/datum/action/cooldown/flock/ping/is_valid_target(atom/cast_on)
return get_turf(cast_on)
/datum/action/cooldown/flock/ping/Activate(atom/target)
. = ..()
var/mob/camera/flock/ghost_bird = owner
ghost_bird.flock.ping(target, ghost_bird)
@@ -0,0 +1,44 @@
/datum/action/cooldown/flock/gatecrash
name = "Gatecrash"
desc = "Transmit an override signal to open every door within range."
button_icon_state = "open_door"
cooldown_time = 40 SECONDS
/datum/action/cooldown/flock/gatecrash/Activate(atom/target)
var/list/targets = list()
for(var/obj/machinery/door/airlock/airlock in range(10, get_turf(owner)))
if(airlock.canAIControl())
targets += airlock
for(var/obj/mecha/mech in range(10, get_turf(owner)))
if(mech.occupant)
targets += mech
if(!length(targets))
to_chat(owner, SPAN_NOTICE("No targets in range that can be opened via radio."))
return FALSE
..()
to_chat(owner, SPAN_NOTICE("You force open all powered doors around you."))
playsound(owner, 'sound/goonstation/flockmind/flockmind_cast.ogg', 50)
addtimer(CALLBACK(src, PROC_REF(async_open_doors), targets), 1.5 SECONDS)
return TRUE
/datum/action/cooldown/flock/gatecrash/proc/async_open_doors(list/doors)
for(var/obj/target in doors)
if(QDELETED(target))
continue
// Stagger openins a little randomly
if(prob(20))
sleep(0.2 SECONDS)
if(isairlock(target))
var/obj/machinery/door/airlock/airlock = target
INVOKE_ASYNC(airlock, TYPE_PROC_REF(/obj/machinery/door, open))
if(ismecha(target))
var/obj/mecha/mech = target
INVOKE_ASYNC(mech, TYPE_PROC_REF(/obj/mecha, go_out))
@@ -0,0 +1,83 @@
/datum/action/cooldown/flock/partition_mind
name = "Partition Mind"
button_icon_state = "partition_mind"
cooldown_time = 60 SECONDS
var/awaiting_partition = FALSE
/datum/action/cooldown/flock/partition_mind/New()
..()
desc = "Divide our computational power, creating a Flocktrace. Requires [FLOCK_COMPUTE_COST_FLOCKTRACE] total bandwidth per trace."
/datum/action/cooldown/flock/partition_mind/is_valid_target(atom/cast_on)
var/mob/camera/flock/overmind/ghost_bird = owner
if(awaiting_partition)
to_chat(ghost_bird, SPAN_WARNING("We are currently partitioning."))
return FALSE
if(!ghost_bird.flock.can_afford(FLOCK_COMPUTE_COST_FLOCKTRACE))
to_chat(ghost_bird, SPAN_WARNING("The Flock does not have enough spare computaional power to support another thread."))
return FALSE
if(length(ghost_bird.flock.traces) >= ghost_bird.flock.max_traces)
if(length(ghost_bird.flock.traces) < floor(FLOCK_COMPUTE_COST_RELAY / FLOCK_COMPUTE_COST_FLOCKTRACE))
to_chat(ghost_bird, SPAN_WARNING("The Flock needs more total computational power to support another thread."))
else
to_chat(ghost_bird, SPAN_WARNING("The Flock has hit it's thread limit."))
return FALSE
return TRUE
/datum/action/cooldown/flock/partition_mind/Activate(atom/target)
. = ..()
do_partition()
/datum/action/cooldown/flock/partition_mind/proc/do_partition()
set waitfor = FALSE
var/mob/camera/flock/overmind/ghost_bird = owner
if(!ghost_bird.flock.can_afford(FLOCK_COMPUTE_COST_FLOCKTRACE))
to_chat(ghost_bird, SPAN_WARNING("Partition failure: bandwidth required is unavailable."))
return
awaiting_partition = TRUE
to_chat(ghost_bird, SPAN_NOTICE("Partitioning initiated, stand by..."))
log_admin("Sending Flocktrace offer to ghosts, they have [30 SECONDS] to respond.")
var/list/candidates = SSghost_spawns.poll_candidates("Do you want to play as a Flocktrace?", ROLE_FLOCK, TRUE, source = /mob/camera/flock/trace)
awaiting_partition = FALSE
if(QDELETED(owner))
log_admin("The Flockmind lost before the partition could complete.")
return
if(!length(candidates))
log_admin("No ghosts responded to the flocktrace offer from [ghost_bird.real_name]")
to_chat(ghost_bird, SPAN_WARNING("Partition failure: unable to coalesce sentience."))
return
if(!ghost_bird.flock.can_afford(FLOCK_COMPUTE_COST_FLOCKTRACE))
log_admin("The Flock was unable to support another flocktrace, partition aborted.")
to_chat(ghost_bird, SPAN_WARNING("Partition failure: bandwidth required is unavailable."))
return
var/mob/candidate = pick(candidates)
dust_if_respawnable(candidate)
log_admin("[key_name_admin(candidate)] respawned as a Flocktrace.")
var/mob/camera/flock/trace/new_ghostbird = new(get_turf(ghost_bird), ghost_bird.flock)
new_ghostbird.key = candidate.key
new_ghostbird.mind = new
new_ghostbird.mind.bind_to(new_ghostbird)
new_ghostbird.mind.set_original_mob(new_ghostbird)
new_ghostbird.mind.assigned_role = SPECIAL_ROLE_FLOCK
new_ghostbird.mind.special_role = SPECIAL_ROLE_FLOCK
var/list/messages = list()
messages += "<div style='font-size: 200%;text-align: center'>You are [gradient_text("The Divine Flock","#3cb5a3", "#124e43")]</div>"
messages += "<div style='text-align: center'>" + gradient_text("We have been partitioned by our overmind to further their goals of propogating The Signal. It is our task to assist in managing their drones to achieve their goals. Such is the will of the Monarch.", "#3cb5a3", "#1e806e") + "</div>"
messages += "<div style='text-align: center'>" + gradient_text("Our powers are more limited than that of our overmind, but we can utilize gatecrash subsystems and take manual control of drones (using alt-click).", "#3cb5a3", "#1e806e") + "</div>"
messages += "<div style='text-align: center'>" + gradient_text("For more information, we have decrypted an information manifest: ([GLOB.configuration.url.wiki_url]/index.php/The_Divine_Flock)", "#3cb5a3", "#1e806e") + "</div>"
to_chat(new_ghostbird, chat_box_red(messages.Join("<br>")))
new_ghostbird.playsound_local(new_ghostbird, 'sound/goonstation/flockmind/ArtifactFea2.ogg', 50, FALSE, use_reverb = FALSE)
SSticker.mode.traitors |= new_ghostbird.mind
@@ -0,0 +1,39 @@
/datum/action/cooldown/flock/radio_blast
name = "Radio Stun Burst"
desc = "Overwhelm the radio headsets of everyone within 3m of your target. Will not work on broken or non-existent headsets."
button_icon_state = "radio_stun"
cooldown_time = 20 SECONDS
click_to_activate = TRUE
/datum/action/cooldown/flock/radio_blast/Activate(atom/target)
var/list/targets = list()
for(var/mob/living/carbon/human/guy in range(3, get_turf(target)))
var/obj/item/radio/worn_radio
for(var/obj/item/I in guy.get_contents())
if(istype(I, /obj/item/radio))
worn_radio = I
break
if(!istype(worn_radio) || !guy.can_hear() || !worn_radio.listening)
continue
targets += guy
if(!length(targets))
to_chat(owner, SPAN_ALERT("No targets are in range with active headsets."))
return FALSE
. = ..()
to_chat(owner, SPAN_NOTICE("You transmit the worst static you can weave into the headsets around you."))
playsound(owner, 'sound/goonstation/flockmind/flockmind_cast.ogg', 50, FALSE)
for(var/mob/living/carbon/human/guy as anything in targets)
to_chat(guy, SPAN_ALERT("Horrifying static bursts into your headset, disorienting you severely!"))
playsound(guy, "sound/effects/radio_sweep[rand(1,5)].ogg", 50, TRUE, SHORT_RANGE_SOUND_EXTRARANGE)
guy.KnockDown(3 SECONDS)
guy.SetConfused(20 SECONDS)
guy.do_jitter_animation(10)
var/obj/item/organ/internal/ears/ears = guy.get_int_organ(/obj/item/organ/internal/ears)
if(istype(ears))
ears.receive_damage(rand(3, 6))
@@ -0,0 +1,40 @@
/datum/action/cooldown/flock/radio_talk
name = "Narrowbeam Transmission"
desc = "Hijack a radio headset of a target to broadcast a message."
button_icon_state = "talk"
cooldown_time = 2 SECONDS
click_to_activate = TRUE
/datum/action/cooldown/flock/radio_talk/Activate(atom/target)
var/obj/item/radio/worn_radio
if(!ishuman(target) && !istype(target, /obj/item/radio))
to_chat(owner, SPAN_ALERT("[target] is not a valid target!"))
return FALSE
if(istype(target, /obj/item/radio))
worn_radio = target
if(!worn_radio.listening)
to_chat(owner, SPAN_ALERT("[target] is not turned on!"))
return FALSE
if(ishuman(target))
var/mob/living/carbon/human/schmuck = target
for(var/obj/item/I in schmuck.get_contents())
if(istype(I, /obj/item/radio))
worn_radio = I
break
if(!istype(worn_radio) || !worn_radio.listening)
to_chat(owner, SPAN_ALERT("[target] does not have a working radio!"))
return FALSE
var/msg = tgui_input_text(usr, "What do you wish to broadcast?", null, "")
if(!msg)
return FALSE
. = ..()
playsound(get_turf(worn_radio), "sound/effects/radio_sweep[rand(1,5)].ogg", 20, TRUE, SHORT_RANGE_SOUND_EXTRARANGE)
to_chat(owner, SPAN_NOTICE("You transmit a message through [worn_radio]!"))
worn_radio.autosay(istype(target, /obj/item/radio) ? Gibberish(msg, 70, 50) : msg, "Strange Static", null)
@@ -0,0 +1,13 @@
/datum/action/cooldown/flock/release_control
name = "Release Control"
desc = "Release yourself from this drone."
button_icon_state = "eject"
/datum/action/cooldown/flock/release_control/Activate(atom/target)
. = ..()
var/mob/living/basic/flock/drone/bird = owner
if(!bird.flock)
to_chat(bird, SPAN_WARNING("You have no flock to return to."))
return
bird.release_control(TRUE)
@@ -0,0 +1,19 @@
/datum/ai_behavior/flock
/// The name of the behavior in the UI for flock drones.
var/name = ""
/datum/ai_behavior/flock/perform(seconds_per_tick, datum/ai_controller/controller, ...)
SHOULD_CALL_PARENT(TRUE)
. = ..()
var/mob/living/basic/flock/drone/bird = controller.pawn
bird.set_task_desc(name)
/datum/ai_behavior/flock/finish_action(datum/ai_controller/controller, succeeded, ...)
var/mob/living/basic/flock/drone/bird = controller.pawn
if(HAS_TRAIT(controller.pawn, TRAIT_FLOCKPHASE))
bird.stop_flockphase(FALSE)
. = ..()
bird.set_task_desc("")
@@ -0,0 +1,86 @@
/datum/ai_behavior/flock/find_capture_target
name = "capturing"
goap_weight = FLOCK_BEHAVIOR_WEIGHT_CAPTURE
/datum/ai_behavior/flock/find_capture_target/setup(datum/ai_controller/controller, mob/overmind_target)
. = ..()
if(overmind_target)
var/mob/living/basic/flock/drone/bird = controller.pawn
if(isturf(overmind_target.loc))
controller.set_blackboard_key(BB_FLOCK_OVERMIND_CONTROL, TRUE)
controller.set_blackboard_key(BB_PATH_MAX_LENGTH, 200)
bird.say("instruction confirmed: capture biological lifeform", forced = TRUE)
else
bird.say("invalid capture target provided by sentient-level instruction", forced = TRUE)
return FALSE
/datum/ai_behavior/flock/find_capture_target/goap_precondition(datum/ai_controller/controller)
var/mob/living/basic/flock/drone/bird = controller.pawn
return length(bird.flock?.enemies)
/datum/ai_behavior/flock/find_capture_target/goap_get_potential_targets(datum/ai_controller/controller)
var/mob/living/basic/flock/bird = controller.pawn
return bird.flock.enemies.Copy()
/datum/ai_behavior/flock/find_capture_target/goap_is_valid_target(datum/ai_controller/controller, atom/target)
var/mob/living/target_mob = target
return ismob(target_mob) && isturf(target_mob.loc) && (target_mob.incapacitated(ignore_restraints = TRUE, ignore_grab = TRUE) || target_mob.IsKnockedDown())
/datum/ai_behavior/flock/find_capture_target/perform(seconds_per_tick, datum/ai_controller/controller, mob/overmind_target)
..()
var/atom/target = overmind_target || goap_get_ideal_target(controller, set_path = TRUE)
if(!target)
return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED
controller.set_blackboard_key(BB_FLOCK_CAPTURE_TARGET, target)
return AI_BEHAVIOR_SUCCEEDED
/datum/ai_behavior/flock/find_capture_target/finish_action(datum/ai_controller/controller, succeeded, obj/item/overmind_target)
. = ..()
controller.clear_blackboard_key(BB_PATH_TO_USE)
if(!succeeded && overmind_target)
controller.clear_blackboard_key(BB_PATH_MAX_LENGTH)
controller.clear_blackboard_key(BB_FLOCK_OVERMIND_CONTROL)
/datum/ai_behavior/flock/find_capture_target/next_behavior(datum/ai_controller/controller, success)
if(success)
controller.queue_behavior(/datum/ai_behavior/flock/perform_capture)
/datum/ai_behavior/flock/perform_capture
name = "capturing"
behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_REQUIRE_REACH
/datum/ai_behavior/flock/perform_capture/setup(datum/ai_controller/controller, ...)
var/atom/target = controller.blackboard[BB_FLOCK_CAPTURE_TARGET]
set_movement_target(controller, target)
return ..()
/datum/ai_behavior/flock/perform_capture/perform(seconds_per_tick, datum/ai_controller/controller, ...)
..()
var/mob/living/basic/flock/drone/bird = controller.pawn
var/mob/living/target = controller.blackboard[BB_FLOCK_CAPTURE_TARGET]
if(target)
if(!isturf(target?.loc) || !(target.incapacitated(ignore_restraints = TRUE, ignore_grab = TRUE)) || target.IsKnockedDown())
return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED
controller.clear_blackboard_key(BB_FLOCK_CAPTURE_TARGET)
var/datum/action/cooldown/flock/cage_mob/cage_action = locate() in bird.actions
spawn(-1)
cage_action.Trigger(target = target)
if(DOING_INTERACTION(bird, "flock_cage"))
return AI_BEHAVIOR_DELAY
return AI_BEHAVIOR_SUCCEEDED
/datum/ai_behavior/flock/perform_capture/finish_action(datum/ai_controller/controller, succeeded, ...)
. = ..()
controller.clear_blackboard_key(BB_FLOCK_CAPTURE_TARGET)
if(!succeeded && controller.blackboard[BB_FLOCK_OVERMIND_CONTROL] && !QDELETED(controller.pawn))
var/mob/living/basic/flock/bird = controller.pawn
bird.say("unable to reach target provided by sentient level instruction, aborting subroutine", forced = TRUE)
controller.clear_blackboard_key(BB_PATH_MAX_LENGTH)
controller.clear_blackboard_key(BB_FLOCK_OVERMIND_CONTROL)
@@ -0,0 +1,158 @@
/datum/ai_behavior/flock/find_conversion_target
name = "building"
goap_weight
/datum/ai_behavior/flock/find_conversion_target/setup(datum/ai_controller/controller, turf/overmind_target)
. = ..()
if(overmind_target)
var/mob/living/basic/flock/drone/bird = controller.pawn
if(goap_is_valid_target(controller, overmind_target))
controller.set_blackboard_key(BB_FLOCK_OVERMIND_CONTROL, TRUE)
controller.set_blackboard_key(BB_PATH_MAX_LENGTH, 200)
bird.say("instruction confirmed: convert object to substrate", forced = TRUE)
else
bird.say("invalid conversion target provided by sentient-level instruction", forced = TRUE)
return FALSE
/datum/ai_behavior/flock/find_conversion_target/goap_precondition(datum/ai_controller/controller)
var/mob/living/basic/flock/bird = controller.pawn
return bird.substrate.has_points(FLOCK_SUBSTRATE_COST_CONVERT)
/datum/ai_behavior/flock/find_conversion_target/score_distance(datum/ai_controller/controller, atom/target)
. = ..()
var/mob/living/basic/flock/bird = controller.pawn
if(bird.flock?.marked_for_conversion[target])
/*
* because the result of scoring is based on max distance,
* the score of any given tile is -100 to 0, with 0 being best.
* Adding 200 basically allows a tile at twice the max distance to be considered.
*/
. += 200
/datum/ai_behavior/flock/find_conversion_target/goap_get_potential_targets(datum/ai_controller/controller)
var/mob/living/basic/flock/bird = controller.pawn
var/datum/flock/bird_flock = bird.flock
var/list/options = list()
var/list/priority_turfs = bird_flock?.get_priority_turfs()
if(length(priority_turfs))
options += priority_turfs
for(var/turf/T as turf in range(controller.target_search_radius, bird))
options += T
return options
/datum/ai_behavior/flock/find_conversion_target/goap_is_valid_target(datum/ai_controller/controller, atom/target)
var/turf/T = target
if(!isturf(T))
return FALSE
if(!T.can_flock_convert())
return FALSE
if(isflockturf(T))
return FALSE
var/mob/living/basic/flock/bird = controller.pawn
if(isnull(bird.flock))
return TRUE
if(bird.flock.claimed_floors[T] || bird.flock.claimed_walls[T])
return FALSE
return bird.flock.is_turf_free(T)
/datum/ai_behavior/flock/find_conversion_target/perform(seconds_per_tick, datum/ai_controller/controller, turf/overmind_target)
..()
var/turf/target = overmind_target || goap_get_ideal_target(controller, TRUE)
if(!target)
return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED
controller.set_blackboard_key(BB_FLOCK_CONVERT_TARGET, target)
var/mob/living/basic/flock/bird = controller.pawn
if(bird.flock)
bird.flock.reserve_turf(bird, target)
return AI_BEHAVIOR_SUCCEEDED
/datum/ai_behavior/flock/find_conversion_target/finish_action(datum/ai_controller/controller, succeeded, turf/overmind_target)
. = ..()
controller.clear_blackboard_key(BB_PATH_TO_USE)
if(!succeeded && overmind_target)
controller.clear_blackboard_key(BB_PATH_MAX_LENGTH)
controller.clear_blackboard_key(BB_FLOCK_OVERMIND_CONTROL)
/datum/ai_behavior/flock/find_conversion_target/next_behavior(datum/ai_controller/controller, success)
if(success)
controller.queue_behavior(/datum/ai_behavior/flock/perform_conversion)
/datum/ai_behavior/flock/perform_conversion
name = "building"
behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT
/datum/ai_behavior/flock/perform_conversion/setup(datum/ai_controller/controller, ...)
var/turf/target_turf = controller.blackboard[BB_FLOCK_CONVERT_TARGET]
set_movement_target(controller, target_turf)
return ..()
/datum/ai_behavior/flock/perform_conversion/perform(seconds_per_tick, datum/ai_controller/controller, ...)
..()
var/mob/living/basic/flock/bird = controller.pawn
var/turf/target = controller.blackboard[BB_FLOCK_CONVERT_TARGET]
if(target)
controller.clear_blackboard_key(BB_FLOCK_CONVERT_TARGET)
var/datum/action/cooldown/flock/convert/convert_action = locate() in bird.actions
spawn(-1)
convert_action.Trigger(target = target)
if(DOING_INTERACTION(bird, "flock_convert"))
return AI_BEHAVIOR_DELAY
return AI_BEHAVIOR_SUCCEEDED
/datum/ai_behavior/flock/perform_conversion/finish_action(datum/ai_controller/controller, succeeded, ...)
. = ..()
var/mob/living/basic/flock/drone/bird = controller.pawn
bird.flock?.free_turf(bird)
if(!succeeded && controller.blackboard[BB_FLOCK_OVERMIND_CONTROL] && !QDELETED(controller.pawn))
bird.say("unable to reach target provided by sentient level instruction, aborting subroutine", forced = TRUE)
controller.clear_blackboard_key(BB_FLOCK_CONVERT_TARGET)
controller.clear_blackboard_key(BB_PATH_MAX_LENGTH)
controller.clear_blackboard_key(BB_FLOCK_OVERMIND_CONTROL)
//
// Subtype for creating a nest. Effectively a higher priority convert that only happens when the bird can lay an egg.
//
/datum/ai_behavior/flock/find_conversion_target/nest
name = "nesting"
goap_weight = FLOCK_BEHAVIOR_WEIGHT_NEST
required_distance = 0
/datum/ai_behavior/flock/find_conversion_target/nest/goap_precondition(datum/ai_controller/controller)
var/mob/living/basic/flock/bird = controller.pawn
if(!bird.flock)
return FALSE
if(length(bird.flock.drones) > FLOCK_DRONE_LIMIT)
return FALSE
return bird.substrate.has_points(FLOCK_SUBSTRATE_COST_CONVERT + bird.flock.current_egg_cost)
/datum/ai_behavior/flock/find_conversion_target/nest/goap_is_valid_target(datum/ai_controller/controller, atom/target)
var/turf/T = target
return ..() && !T.is_blocked_turf(exclude_mobs = TRUE) && !locate(/obj/structure/flock/egg, T)
/datum/ai_behavior/flock/perform_conversion/nest
name = "nesting"
required_distance = 0
/datum/ai_behavior/flock/perform_conversion/nest/next_behavior(datum/ai_controller/controller, success)
. = ..()
if(success)
controller.queue_behavior(/datum/ai_behavior/flock/find_existing_nest)
@@ -0,0 +1,114 @@
/datum/ai_behavior/flock/find_deconstruct_target
name = "deconstructing"
goap_weight = FLOCK_BEHAVIOR_WEIGHT_DECONSTRUCT
/datum/ai_behavior/flock/find_deconstruct_target/setup(datum/ai_controller/controller, turf/overmind_target)
. = ..()
if(overmind_target)
var/mob/living/basic/flock/drone/bird = controller.pawn
if(is_valid_target(overmind_target))
controller.set_blackboard_key(BB_FLOCK_OVERMIND_CONTROL, TRUE)
controller.set_blackboard_key(BB_PATH_MAX_LENGTH, 200)
bird.say("instruction confirmed: deconstruct object", forced = TRUE)
else
bird.say("invalid deconstruct target provided by sentient-level instruction", forced = TRUE)
return FALSE
/datum/ai_behavior/flock/find_deconstruct_target/goap_precondition(datum/ai_controller/controller)
var/mob/living/basic/flock/bird = controller.pawn
return length(bird.flock?.marked_for_deconstruction)
/datum/ai_behavior/flock/find_deconstruct_target/goap_score(datum/ai_controller/controller)
return score_distance(controller, get_target(controller))
/datum/ai_behavior/flock/find_deconstruct_target/score_distance(datum/ai_controller/controller, atom/target)
. = ..()
var/mob/living/basic/flock/bird = controller.pawn
if(bird.flock?.marked_for_deconstruction[target])
/*
* because the result of scoring is based on max distance,
* the score of any given tile is -100 to 0, with 0 being best.
* Adding 200 basically allows a tile at twice the max distance to be considered.
*/
. += 200
/datum/ai_behavior/flock/find_deconstruct_target/proc/get_target(datum/ai_controller/controller, path_to = FALSE)
var/mob/living/basic/flock/bird = controller.pawn
var/list/options = list()
for(var/atom/A as anything in bird.flock.marked_for_deconstruction)
if(get_dist(A, bird) > controller.max_target_distance)
continue
if(bird.flock && !bird.flock.is_turf_free(get_turf(A)))
continue
options += A
return get_best_target_by_distance_score(controller, options, path_to)
/datum/ai_behavior/flock/find_deconstruct_target/proc/is_valid_target(atom/target)
return !ismob(target) && HAS_TRAIT(target, TRAIT_FLOCK_THING) && !HAS_TRAIT(target, TRAIT_FLOCK_NODECON)
/datum/ai_behavior/flock/find_deconstruct_target/perform(seconds_per_tick, datum/ai_controller/controller, turf/overmind_target)
..()
var/atom/target = overmind_target || get_target(controller, TRUE)
if(!target)
return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED
controller.set_blackboard_key(BB_FLOCK_DECON_TARGET, target)
var/mob/living/basic/flock/bird = controller.pawn
if(bird.flock)
bird.flock.reserve_turf(bird, get_turf(target), remove_on_change = FALSE)
return AI_BEHAVIOR_SUCCEEDED
/datum/ai_behavior/flock/find_deconstruct_target/finish_action(datum/ai_controller/controller, succeeded, turf/overmind_target)
. = ..()
controller.clear_blackboard_key(BB_PATH_TO_USE)
if(!succeeded && overmind_target)
controller.clear_blackboard_key(BB_PATH_MAX_LENGTH)
controller.clear_blackboard_key(BB_FLOCK_OVERMIND_CONTROL)
/datum/ai_behavior/flock/find_deconstruct_target/next_behavior(datum/ai_controller/controller, success)
if(success)
controller.queue_behavior(/datum/ai_behavior/flock/perform_deconstruct)
/datum/ai_behavior/flock/perform_deconstruct
name = "deconstructing"
behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT
/datum/ai_behavior/flock/perform_deconstruct/setup(datum/ai_controller/controller, ...)
var/turf/target_turf = controller.blackboard[BB_FLOCK_DECON_TARGET]
set_movement_target(controller, target_turf)
return ..()
/datum/ai_behavior/flock/perform_deconstruct/perform(seconds_per_tick, datum/ai_controller/controller, ...)
..()
var/mob/living/basic/flock/bird = controller.pawn
var/turf/target = controller.blackboard[BB_FLOCK_DECON_TARGET]
if(target)
controller.clear_blackboard_key(BB_FLOCK_DECON_TARGET)
var/datum/action/cooldown/flock/deconstruct/deconstruct_action = locate() in bird.actions
spawn(-1)
deconstruct_action.Trigger(target = target)
if(DOING_INTERACTION(bird, "flock_deconstruct"))
return AI_BEHAVIOR_DELAY
return AI_BEHAVIOR_SUCCEEDED
/datum/ai_behavior/flock/perform_deconstruct/finish_action(datum/ai_controller/controller, succeeded, ...)
. = ..()
var/mob/living/basic/flock/drone/bird = controller.pawn
bird.flock?.free_turf(bird)
if(!succeeded && controller.blackboard[BB_FLOCK_OVERMIND_CONTROL] && !QDELETED(controller.pawn))
bird.say("unable to reach target provided by sentient level instruction, aborting subroutine", forced = TRUE)
controller.clear_blackboard_key(BB_FLOCK_DECON_TARGET)
controller.clear_blackboard_key(BB_PATH_MAX_LENGTH)
controller.clear_blackboard_key(BB_FLOCK_OVERMIND_CONTROL)
@@ -0,0 +1,101 @@
/datum/ai_behavior/flock/find_deposit_target
name = "depositing"
goap_weight = FLOCK_BEHAVIOR_WEIGHT_DEPOSIT
/datum/ai_behavior/flock/find_deposit_target/setup(datum/ai_controller/controller, obj/structure/flock/overmind_target)
. = ..()
if(overmind_target)
var/mob/living/basic/flock/drone/bird = controller.pawn
if(goap_is_valid_target(controller, overmind_target))
controller.set_blackboard_key(BB_FLOCK_OVERMIND_CONTROL, TRUE)
controller.set_blackboard_key(BB_PATH_MAX_LENGTH, 200)
bird.say("instruction confirmed: deposit substrate", forced = TRUE)
else
bird.say("invalid deposit target provided by sentient-level instruction", forced = TRUE)
return FALSE
/datum/ai_behavior/flock/find_deposit_target/goap_precondition(datum/ai_controller/controller)
var/mob/living/basic/flock/bird = controller.pawn
return bird.substrate.has_points(FLOCK_SUBSTRATE_COST_DEPOST_TEALPRINT)
/datum/ai_behavior/flock/find_deposit_target/goap_get_potential_targets(datum/ai_controller/controller)
var/list/options = ..()
for(var/obj/structure/flock/tealprint in range(controller.target_search_radius, controller.pawn))
options += tealprint
return options
/datum/ai_behavior/flock/find_deposit_target/goap_is_valid_target(datum/ai_controller/controller, atom/target)
var/obj/structure/flock/tealprint/tealprint = target
if(!istype(tealprint))
return FALSE
var/mob/living/basic/flock/bird = controller.pawn
return (tealprint.flock == bird.flock) && !tealprint.substrate.is_full()
/datum/ai_behavior/flock/find_deposit_target/perform(seconds_per_tick, datum/ai_controller/controller, overmind_target)
..()
var/atom/target = overmind_target || goap_get_ideal_target(controller, set_path = TRUE)
if(!target)
return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED
controller.set_blackboard_key(BB_FLOCK_DEPOSIT_TARGET, target)
return AI_BEHAVIOR_SUCCEEDED
/datum/ai_behavior/flock/find_deposit_target/finish_action(datum/ai_controller/controller, succeeded, overmind_target)
. = ..()
controller.clear_blackboard_key(BB_PATH_TO_USE)
if(!succeeded && overmind_target)
controller.clear_blackboard_key(BB_PATH_MAX_LENGTH)
controller.clear_blackboard_key(BB_FLOCK_OVERMIND_CONTROL)
/datum/ai_behavior/flock/find_deposit_target/next_behavior(datum/ai_controller/controller, success)
if(success)
controller.queue_behavior(/datum/ai_behavior/flock/perform_deposit)
/datum/ai_behavior/flock/perform_deposit
name = "depositing"
behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_REQUIRE_REACH
/datum/ai_behavior/flock/perform_deposit/setup(datum/ai_controller/controller, ...)
var/turf/target_turf = controller.blackboard[BB_FLOCK_DEPOSIT_TARGET]
set_movement_target(controller, target_turf)
return ..()
/datum/ai_behavior/flock/perform_deposit/goap_is_valid_target(datum/ai_controller/controller, atom/target)
var/obj/structure/flock/tealprint/tealprint = target
if(!istype(tealprint))
return FALSE
var/mob/living/basic/flock/bird = controller.pawn
return (tealprint.flock == bird.flock) && !tealprint.substrate.is_full()
/datum/ai_behavior/flock/perform_deposit/perform(seconds_per_tick, datum/ai_controller/controller, ...)
..()
var/mob/living/basic/flock/drone/bird = controller.pawn
var/obj/structure/flock/tealprint/target = controller.blackboard[BB_FLOCK_DEPOSIT_TARGET]
if(target)
if(!goap_is_valid_target(controller, target))
return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED
controller.clear_blackboard_key(BB_FLOCK_DEPOSIT_TARGET)
var/datum/action/cooldown/flock/deposit/deposit_action = locate() in bird.actions
spawn(-1)
deposit_action.Trigger(target = target)
if(DOING_INTERACTION(bird, "flock_cage"))
return AI_BEHAVIOR_DELAY
return AI_BEHAVIOR_SUCCEEDED
/datum/ai_behavior/flock/perform_deposit/finish_action(datum/ai_controller/controller, succeeded, ...)
. = ..()
controller.clear_blackboard_key(BB_FLOCK_DEPOSIT_TARGET)
if(!succeeded && controller.blackboard[BB_FLOCK_OVERMIND_CONTROL] && !QDELETED(controller.pawn))
var/mob/living/basic/flock/bird = controller.pawn
bird.say("unable to reach target provided by sentient level instruction, aborting subroutine", forced = TRUE)
controller.clear_blackboard_key(BB_PATH_MAX_LENGTH)
controller.clear_blackboard_key(BB_FLOCK_OVERMIND_CONTROL)
@@ -0,0 +1,88 @@
/datum/ai_behavior/flock/find_harvest_target
name = "harvesting"
goap_weight = FLOCK_BEHAVIOR_WEIGHT_HARVEST
/datum/ai_behavior/flock/find_harvest_target/setup(datum/ai_controller/controller, obj/item/overmind_target)
. = ..()
if(overmind_target)
var/mob/living/basic/flock/drone/bird = controller.pawn
if(isitem(overmind_target) && isturf(overmind_target.loc))
controller.set_blackboard_key(BB_FLOCK_OVERMIND_CONTROL, TRUE)
controller.set_blackboard_key(BB_PATH_MAX_LENGTH, 200)
bird.say("instruction confirmed: convert object to substrate", forced = TRUE)
else
bird.say("invalid harvest target provided by sentient-level instruction", forced = TRUE)
return FALSE
/datum/ai_behavior/flock/find_harvest_target/goap_precondition(datum/ai_controller/controller)
var/mob/living/basic/flock/drone/bird = controller.pawn
var/datum/flockdrone_part/absorber/absorber = locate() in bird.parts
return !absorber.held_item
/datum/ai_behavior/flock/find_harvest_target/goap_score(datum/ai_controller/controller)
return score_distance(controller, get_target(controller))
/datum/ai_behavior/flock/find_harvest_target/proc/get_target(datum/ai_controller/controller, path_to = FALSE)
var/mob/living/basic/flock/bird = controller.pawn
var/list/options = list()
for(var/obj/item/I in view(controller.target_search_radius, bird))
if(isturf(I.loc) && !I.anchored)
options += I
return get_best_target_by_distance_score(controller, options, path_to)
/datum/ai_behavior/flock/find_harvest_target/perform(seconds_per_tick, datum/ai_controller/controller, obj/item/overmind_target)
..()
var/atom/target = overmind_target || get_target(controller, TRUE)
if(!target)
return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED
controller.set_blackboard_key(BB_FLOCK_HARVEST_TARGET, target)
return AI_BEHAVIOR_SUCCEEDED
/datum/ai_behavior/flock/find_harvest_target/finish_action(datum/ai_controller/controller, succeeded, obj/item/overmind_target)
. = ..()
controller.clear_blackboard_key(BB_PATH_TO_USE)
if(!succeeded && overmind_target)
controller.clear_blackboard_key(BB_PATH_MAX_LENGTH)
controller.clear_blackboard_key(BB_FLOCK_OVERMIND_CONTROL)
/datum/ai_behavior/flock/find_harvest_target/next_behavior(datum/ai_controller/controller, success)
if(success)
controller.queue_behavior(/datum/ai_behavior/flock/perform_harvest)
/datum/ai_behavior/flock/perform_harvest
name = "harvesting"
behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_REQUIRE_REACH
/datum/ai_behavior/flock/perform_harvest/setup(datum/ai_controller/controller, ...)
var/turf/target_turf = controller.blackboard[BB_FLOCK_HARVEST_TARGET]
set_movement_target(controller, target_turf)
return ..()
/datum/ai_behavior/flock/perform_harvest/perform(seconds_per_tick, datum/ai_controller/controller, ...)
..()
var/mob/living/basic/flock/drone/bird = controller.pawn
var/obj/item/target = controller.blackboard[BB_FLOCK_HARVEST_TARGET]
if(!isturf(target?.loc))
return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED
var/datum/flockdrone_part/absorber/absorber = locate() in bird.parts
if(!absorber.try_pickup_item(target))
return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED
return AI_BEHAVIOR_SUCCEEDED
/datum/ai_behavior/flock/perform_harvest/finish_action(datum/ai_controller/controller, succeeded, ...)
. = ..()
controller.clear_blackboard_key(BB_FLOCK_HARVEST_TARGET)
if(!succeeded && controller.blackboard[BB_FLOCK_OVERMIND_CONTROL] && !QDELETED(controller.pawn))
var/mob/living/basic/flock/bird = controller.pawn
bird.say("unable to reach target provided by sentient level instruction, aborting subroutine", forced = TRUE)
controller.clear_blackboard_key(BB_PATH_MAX_LENGTH)
controller.clear_blackboard_key(BB_FLOCK_OVERMIND_CONTROL)
@@ -0,0 +1,90 @@
/datum/ai_behavior/flock/find_heal_target
name = "repairing"
goap_weight = FLOCK_BEHAVIOR_WEIGHT_REPAIR
/datum/ai_behavior/flock/find_heal_target/setup(datum/ai_controller/controller, mob/living/basic/flock/overmind_target)
. = ..()
if(overmind_target)
var/mob/living/basic/flock/drone/bird = controller.pawn
if(goap_is_valid_target(controller, overmind_target))
controller.set_blackboard_key(BB_FLOCK_OVERMIND_CONTROL, TRUE)
controller.set_blackboard_key(BB_PATH_MAX_LENGTH, 200)
bird.say("instruction confirmed: repair construct", forced = TRUE)
else
bird.say("invalid repair target provided by sentient-level instruction", forced = TRUE)
return FALSE
/datum/ai_behavior/flock/find_heal_target/goap_precondition(datum/ai_controller/controller)
var/mob/living/basic/flock/drone/bird = controller.pawn
return bird.flock && bird.substrate.has_points(FLOCK_SUBSTRATE_COST_REPAIR)
/datum/ai_behavior/flock/find_heal_target/goap_get_potential_targets(datum/ai_controller/controller)
var/list/options = ..()
for(var/mob/living/basic/flock/drone/bird in oview(controller.target_search_radius, controller.pawn))
options += bird
return options
/datum/ai_behavior/flock/find_heal_target/goap_is_valid_target(datum/ai_controller/controller, atom/target)
var/mob/living/basic/flock/drone/this_bird = controller.pawn
var/mob/living/basic/flock/drone/other_bird = target
if(this_bird.flock != other_bird.flock)
return FALSE
if(other_bird.stat == DEAD)
return FALSE
// Birds at or below 60% health
if((other_bird.getBruteLoss() + other_bird.getFireLoss()) / other_bird.maxHealth >= 0.4)
return TRUE
/datum/ai_behavior/flock/find_heal_target/perform(seconds_per_tick, datum/ai_controller/controller, mob/living/basic/flock/overmind_target)
..()
var/atom/target = overmind_target || goap_get_ideal_target(controller, set_path = TRUE)
if(!target)
return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED
controller.set_blackboard_key(BB_FLOCK_HEAL_TARGET, target)
return AI_BEHAVIOR_SUCCEEDED
/datum/ai_behavior/flock/find_heal_target/finish_action(datum/ai_controller/controller, succeeded, overmind_target)
. = ..()
controller.clear_blackboard_key(BB_PATH_TO_USE)
if(!succeeded && overmind_target)
controller.clear_blackboard_key(BB_PATH_MAX_LENGTH)
controller.clear_blackboard_key(BB_FLOCK_OVERMIND_CONTROL)
/datum/ai_behavior/flock/find_heal_target/next_behavior(datum/ai_controller/controller, success)
if(success)
controller.queue_behavior(/datum/ai_behavior/flock/heal)
/datum/ai_behavior/flock/heal
name = "repairing"
behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT
/datum/ai_behavior/flock/heal/setup(datum/ai_controller/controller, ...)
var/atom/target = controller.blackboard[BB_FLOCK_HEAL_TARGET]
set_movement_target(controller, target)
return ..()
/datum/ai_behavior/flock/heal/perform(seconds_per_tick, datum/ai_controller/controller, ...)
..()
var/mob/living/basic/flock/drone/bird = controller.pawn
if(isnull(controller.blackboard[BB_FLOCK_HEAL_FRUSTRATION]))
controller.set_blackboard_key(BB_FLOCK_HEAL_FRUSTRATION, world.time + 3 SECONDS)
if(DOING_INTERACTION(bird, "flock_repair"))
return AI_BEHAVIOR_DELAY
var/datum/action/cooldown/flock/flock_heal/repair = locate() in bird.actions
spawn(-1)
repair.Trigger(target = controller.blackboard[BB_FLOCK_HEAL_TARGET])
if(controller.blackboard[BB_FLOCK_HEAL_FRUSTRATION] >= world.time)
return AI_BEHAVIOR_DELAY
return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED
/datum/ai_behavior/flock/heal/finish_action(datum/ai_controller/controller, succeeded, ...)
. = ..()
controller.clear_blackboard_key(BB_FLOCK_HEAL_FRUSTRATION)
controller.clear_blackboard_key(BB_FLOCK_HEAL_TARGET)
@@ -0,0 +1,63 @@
/datum/ai_behavior/flock/find_closed_container
name = "opening container"
goap_weight = FLOCK_BEHAVIOR_WEIGHT_OPEN_CONTAINER
/datum/ai_behavior/flock/find_closed_container/goap_score(datum/ai_controller/controller)
return score_distance(controller, get_target(controller))
/datum/ai_behavior/flock/find_closed_container/goap_precondition(datum/ai_controller/controller)
var/mob/living/basic/flock/drone/bird = controller.pawn
var/datum/flockdrone_part/absorber/absorber = locate() in bird.parts
return !absorber.held_item
/datum/ai_behavior/flock/find_closed_container/proc/get_target(datum/ai_controller/controller, path_to = FALSE)
var/mob/living/basic/flock/bird = controller.pawn
var/list/options = list()
for(var/obj/structure/closet/container in view(controller.target_search_radius, bird))
if(container.opened || container.welded || container.locked)
continue
options += container
return get_best_target_by_distance_score(controller, options, path_to)
/datum/ai_behavior/flock/find_closed_container/perform(seconds_per_tick, datum/ai_controller/controller, turf/overmind_target)
..()
var/atom/target = get_target(controller, TRUE)
if(!target)
return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED
controller.set_blackboard_key(BB_FLOCK_CONTAINER_TARGET, target)
return AI_BEHAVIOR_SUCCEEDED
/datum/ai_behavior/flock/find_closed_container/finish_action(datum/ai_controller/controller, succeeded, turf/overmind_target)
. = ..()
controller.clear_blackboard_key(BB_PATH_TO_USE)
/datum/ai_behavior/flock/find_closed_container/next_behavior(datum/ai_controller/controller, success)
if(success)
controller.queue_behavior(/datum/ai_behavior/flock/perform_open_container)
/datum/ai_behavior/flock/perform_open_container
name = "opening container"
behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_REQUIRE_REACH
/datum/ai_behavior/flock/perform_open_container/setup(datum/ai_controller/controller, ...)
var/atom/target = controller.blackboard[BB_FLOCK_CONTAINER_TARGET]
set_movement_target(controller, target)
return ..()
/datum/ai_behavior/flock/perform_open_container/perform(seconds_per_tick, datum/ai_controller/controller, ...)
..()
var/mob/living/basic/flock/bird = controller.pawn
var/obj/structure/closet/target = controller.blackboard[BB_FLOCK_CONTAINER_TARGET]
if(target)
target.open(bird, TRUE)
else
return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED
return AI_BEHAVIOR_SUCCEEDED
/datum/ai_behavior/flock/perform_open_container/finish_action(datum/ai_controller/controller, succeeded, ...)
. = ..()
controller.clear_blackboard_key(BB_FLOCK_CONTAINER_TARGET)
@@ -0,0 +1,17 @@
/datum/ai_behavior/flock/rally
name = "rallying"
behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT
required_distance = 0
/datum/ai_behavior/flock/rally/setup(datum/ai_controller/controller, turf/destination)
. = ..()
controller.set_blackboard_key(BB_PATH_MAX_LENGTH, 200)
controller.set_movement_target(controller, destination)
/datum/ai_behavior/flock/rally/perform(seconds_per_tick, datum/ai_controller/controller, turf/destination)
..()
return AI_BEHAVIOR_SUCCEEDED
/datum/ai_behavior/flock/rally/finish_action(datum/ai_controller/controller, succeeded, ...)
. = ..()
controller.clear_blackboard_key(BB_PATH_MAX_LENGTH)
@@ -0,0 +1,69 @@
// Action sequence for laying an egg. If there are no open flock tiles nearby, the "nest" behavior will attempt to create one.
/datum/ai_behavior/flock/find_existing_nest
name = "replicating"
required_distance = 0
goap_weight = FLOCK_BEHAVIOR_WEIGHT_REPLICATE
/datum/ai_behavior/flock/find_existing_nest/goap_precondition(datum/ai_controller/controller)
var/mob/living/basic/flock/drone/bird = controller.pawn
if(!bird.flock)
return FALSE
return length(bird.flock.drones) < FLOCK_DRONE_LIMIT && bird.substrate.has_points(FLOCK_SUBSTRATE_COST_CONVERT + bird.flock.get_egg_elligibility_cost())
/datum/ai_behavior/flock/find_existing_nest/goap_score(datum/ai_controller/controller)
return score_distance(controller, get_target(controller))
/datum/ai_behavior/flock/find_existing_nest/proc/get_target(datum/ai_controller/controller, path_to = FALSE)
var/list/options = list()
for(var/turf/simulated/floor/flock/T in range(controller.target_search_radius, controller.pawn))
if(!T.is_blocked_turf(source_atom = controller.pawn) && !locate(/obj/structure/flock/egg, T))
options += T
return get_best_target_by_distance_score(controller, options, path_to)
/datum/ai_behavior/flock/find_existing_nest/perform(seconds_per_tick, datum/ai_controller/controller)
..()
var/turf/target = get_target(controller, TRUE)
if(!target)
return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED
controller.set_blackboard_key(BB_FLOCK_REPLICATE_TARGET, target)
return AI_BEHAVIOR_SUCCEEDED
/datum/ai_behavior/flock/find_existing_nest/finish_action(datum/ai_controller/controller, succeeded, turf/overmind_target)
. = ..()
controller.clear_blackboard_key(BB_PATH_TO_USE)
/datum/ai_behavior/flock/next_behavior(datum/ai_controller/controller, success)
if(success)
controller.queue_behavior(/datum/ai_behavior/flock/perform_replicate)
/datum/ai_behavior/flock/perform_replicate
name = "replicating"
behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT
required_distance = 0
/datum/ai_behavior/flock/perform_replicate/setup(datum/ai_controller/controller, ...)
var/atom/target = controller.blackboard[BB_FLOCK_REPLICATE_TARGET]
set_movement_target(controller, target)
return ..()
/datum/ai_behavior/flock/perform_replicate/perform(seconds_per_tick, datum/ai_controller/controller, ...)
. = ..()
var/mob/living/basic/flock/bird = controller.pawn
var/turf/target = controller.blackboard[BB_FLOCK_REPLICATE_TARGET]
if(target)
controller.clear_blackboard_key(BB_FLOCK_REPLICATE_TARGET)
var/datum/action/cooldown/flock/nest/nest_action = locate() in bird.actions
spawn(-1)
nest_action.Trigger(target = target)
if(DOING_INTERACTION(bird, "flock_lay_egg"))
return AI_BEHAVIOR_DELAY
return AI_BEHAVIOR_SUCCEEDED
/datum/ai_behavior/flock/perform_nest/finish_action(datum/ai_controller/controller, succeeded, ...)
. = ..()
controller.clear_blackboard_key(BB_FLOCK_REPLICATE_TARGET)
@@ -0,0 +1,17 @@
/datum/ai_behavior/flock/resist
name = "resisting"
action_cooldown = 15 SECONDS
behavior_flags = AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION
/datum/ai_behavior/flock/resist/goap_score(datum/ai_controller/controller)
var/mob/living/living_pawn = controller.pawn
if(SHOULD_RESIST(living_pawn) || length(living_pawn.grabbed_by) || isobj(living_pawn.loc))
return 5
return 0
/datum/ai_behavior/flock/resist/perform(seconds_per_tick, datum/ai_controller/controller, ...)
..()
controller.queue_behavior(/datum/ai_behavior/resist)
return AI_BEHAVIOR_SUCCEEDED
@@ -0,0 +1,80 @@
/datum/ai_behavior/flock/find_storage_item
name = "rummaging"
goap_weight = FLOCK_BEHAVIOR_WEIGHT_RUMMAGE
/datum/ai_behavior/flock/find_storage_item/goap_score(datum/ai_controller/controller)
return score_distance(controller, get_target(controller))
/datum/ai_behavior/flock/find_storage_item/goap_precondition(datum/ai_controller/controller)
var/mob/living/basic/flock/drone/bird = controller.pawn
var/datum/flockdrone_part/absorber/absorber = locate() in bird.parts
return !absorber.held_item
/datum/ai_behavior/flock/find_storage_item/proc/get_target(datum/ai_controller/controller, path_to = FALSE)
var/mob/living/basic/flock/bird = controller.pawn
var/list/options = list()
for(var/obj/item/storage/item in view(controller.target_search_radius, bird))
if(!length(item.contents))
continue
options += item
return get_best_target_by_distance_score(controller, options, path_to)
/datum/ai_behavior/flock/find_storage_item/perform(seconds_per_tick, datum/ai_controller/controller, turf/overmind_target)
..()
var/atom/target = get_target(controller, TRUE)
if(!target)
return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED
controller.set_blackboard_key(BB_FLOCK_RUMMAGE_TARGET, target)
return AI_BEHAVIOR_SUCCEEDED
/datum/ai_behavior/flock/find_storage_item/finish_action(datum/ai_controller/controller, succeeded, turf/overmind_target)
. = ..()
controller.clear_blackboard_key(BB_PATH_TO_USE)
/datum/ai_behavior/flock/find_storage_item/next_behavior(datum/ai_controller/controller, success)
if(success)
controller.queue_behavior(/datum/ai_behavior/flock/perform_rummage)
/datum/ai_behavior/flock/perform_rummage
name = "rummaging"
behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_REQUIRE_REACH
/datum/ai_behavior/flock/perform_rummage/setup(datum/ai_controller/controller, ...)
var/atom/target = controller.blackboard[BB_FLOCK_RUMMAGE_TARGET]
set_movement_target(controller, target)
return ..()
/datum/ai_behavior/flock/perform_rummage/perform(seconds_per_tick, datum/ai_controller/controller, ...)
..()
var/mob/living/basic/flock/bird = controller.pawn
var/obj/item/target = controller.blackboard[BB_FLOCK_RUMMAGE_TARGET]
if(!target)
return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED
if(DOING_INTERACTION(bird, "flock_rummage"))
return AI_BEHAVIOR_DELAY
rummage_till_empty(controller, bird, target)
return AI_BEHAVIOR_SUCCEEDED
/datum/ai_behavior/flock/perform_rummage/finish_action(datum/ai_controller/controller, succeeded, ...)
. = ..()
controller.clear_blackboard_key(BB_FLOCK_RUMMAGE_TARGET)
/datum/ai_behavior/flock/perform_rummage/proc/rummage_till_empty(datum/ai_controller/controller, mob/living/basic/flock/bird, obj/item/target)
set waitfor = FALSE
while(target == controller.blackboard[BB_FLOCK_RUMMAGE_TARGET] && length(target.contents))
if(!do_after(bird, 1 SECONDS, target = target, interaction_key = "flock_rummage"))
return
if(target != controller.blackboard[BB_FLOCK_RUMMAGE_TARGET] || !length(target.contents))
return
var/obj/item/contained_item = target.contents[1]
contained_item.forceMove(bird.drop_location())
@@ -0,0 +1,91 @@
/datum/ai_behavior/flock/attack_target
name = "incapacitating"
behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_MOVE_AND_PERFORM
goap_weight = FLOCK_BEHAVIOR_WEIGHT_SHOOT
search_radius_override = 12
/datum/ai_behavior/flock/attack_target/setup(datum/ai_controller/controller, mob/overmind_target)
. = ..()
if(overmind_target)
var/mob/living/basic/flock/drone/bird = controller.pawn
if(!goap_is_valid_target(controller, overmind_target))
controller.set_blackboard_key(BB_FLOCK_OVERMIND_CONTROL, TRUE)
controller.set_blackboard_key(BB_PATH_MAX_LENGTH, 200)
bird.say("instruction confirmed: incapacitate lifeform", forced = TRUE)
else
bird.say("invalid attack target provided by sentient-level instruction", forced = TRUE)
return FALSE
var/atom/target = overmind_target || goap_get_ideal_target(controller, set_path = TRUE)
if(!target)
return FALSE
controller.set_blackboard_key(BB_FLOCK_ATTACK_TARGET, target)
set_movement_target(controller, target)
/datum/ai_behavior/flock/attack_target/goap_precondition(datum/ai_controller/controller)
var/mob/living/basic/flock/drone/bird = controller.pawn
var/list/enemies = bird.flock?.enemies.Copy()
for(var/obj/mecha/M in view(search_radius_override, controller.pawn))
enemies += M
return length(enemies)
/datum/ai_behavior/flock/attack_target/goap_get_potential_targets(datum/ai_controller/controller)
var/mob/living/basic/flock/bird = controller.pawn
var/list/enemies = bird.flock.enemies.Copy()
for(var/obj/mecha/M in view(search_radius_override, controller.pawn))
enemies += M
return enemies
/datum/ai_behavior/flock/attack_target/goap_is_valid_target(datum/ai_controller/controller, atom/target)
if(ismecha(target))
return TRUE
var/mob/living/target_mob = target
return ismob(target_mob) && isturf(target_mob.loc) && !target_mob.incapacitated(ignore_restraints = TRUE, ignore_grab = TRUE) && !target_mob.IsKnockedDown()
/datum/ai_behavior/flock/attack_target/perform(seconds_per_tick, datum/ai_controller/controller, mob/overmind_target)
..()
var/atom/target = controller.blackboard[BB_FLOCK_ATTACK_TARGET]
if(!target)
return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED
if(!goap_is_valid_target(controller, target))
return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED
if(!COOLDOWN_FINISHED(controller, blackboard[BB_FLOCK_ATTACK_COOLDOWN]))
controller.set_blackboard_key(BB_FLOCK_ATTACK_FRUSTRATION, world.time) // Reset frustration if we're on cooldown.
return AI_BEHAVIOR_DELAY
var/mob/living/basic/flock/drone/bird = controller.pawn
if(!can_see(bird, target))
return AI_BEHAVIOR_DELAY
if(get_dist(bird, target) > 6) // Too far to shoot!
return AI_BEHAVIOR_DELAY
// Run away!
if(SPT_PROB(40, seconds_per_tick) && COOLDOWN_FINISHED(controller, blackboard[BB_FLOCK_ATTACK_RUN_COOLDOWN]) && get_dist(bird, target) < 3)
controller.set_blackboard_key(BB_FLOCK_ATTACK_RUN_COOLDOWN, world.time + 5 SECONDS)
controller.queue_behavior(/datum/ai_behavior/run_away_from_target, target)
// Strafe!
else if(SPT_PROB(60, seconds_per_tick) && COOLDOWN_FINISHED(controller, blackboard[BB_FLOCK_ATTACK_STRAFE_COOLDOWN]))
controller.set_blackboard_key(BB_FLOCK_ATTACK_STRAFE_COOLDOWN, controller.movement_delay * 3)
var/step_dir = turn(get_dir(bird, target), prob(50) ? 90 : -90)
step(bird, step_dir)
if(!istype(bird.active_part, /datum/flockdrone_part/incapacitator))
var/datum/flockdrone_part/incapacitator/weapon = locate() in bird.parts
bird.set_active_part(weapon)
controller.set_blackboard_key(BB_FLOCK_ATTACK_COOLDOWN, world.time + 1.2 SECONDS)
controller.set_blackboard_key(BB_FLOCK_ATTACK_FRUSTRATION, world.time) // Reset frustration
bird.RangedAttack(target)
return AI_BEHAVIOR_DELAY
/datum/ai_behavior/flock/attack_target/finish_action(datum/ai_controller/controller, succeeded, ...)
. = ..()
controller.clear_blackboard_key(BB_FLOCK_ATTACK_TARGET)
controller.clear_blackboard_key(BB_FLOCK_ATTACK_FRUSTRATION)
@@ -0,0 +1,49 @@
/datum/ai_behavior/flock/stare
name = "analyzing"
action_cooldown = 20 SECONDS
behavior_flags = AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION
/datum/ai_behavior/flock/stare/goap_score(datum/ai_controller/controller)
if(controller.behavior_cooldowns[src] > world.time)
return 0
return length(get_targets(controller)) > 0
/datum/ai_behavior/flock/stare/get_cooldown(datum/ai_controller/cooldown_for)
return rand(2, 3) MINUTES
/datum/ai_behavior/flock/stare/proc/get_targets(datum/ai_controller/controller)
. = list()
for(var/mob/living/viewer in viewers(controller.pawn, controller.target_search_radius))
if(isvox(viewer) || istype(viewer, /mob/living/simple_animal/parrot))
. += viewer
/datum/ai_behavior/flock/stare/perform(seconds_per_tick, datum/ai_controller/controller, ...)
..()
var/list/targets = get_targets(controller)
if(length(targets))
controller.set_blackboard_key(BB_FLOCK_STARE_TARGET, pick(targets))
return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED
return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED
/datum/ai_behavior/flock/stare/next_behavior(datum/ai_controller/controller, success)
if(success)
return controller.queue_behavior(/datum/ai_behavior/flock/stare_at_bird)
/datum/ai_behavior/flock/stare_at_bird
name = "analyzing"
action_cooldown = 20 SECONDS
behavior_flags = AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION
/datum/ai_behavior/flock/stare_at_bird/perform(seconds_per_tick, datum/ai_controller/controller, ...)
..()
var/mob/living/living_pawn = controller.pawn
living_pawn.face_atom(controller.blackboard[BB_FLOCK_STARE_TARGET])
var/atom/A = controller.blackboard[BB_FLOCK_STARE_TARGET]
if(prob(30))
living_pawn.manual_emote("stares at [A].")
controller.clear_blackboard_key(BB_FLOCK_STARE_TARGET)
return AI_BEHAVIOR_SUCCEEDED

Some files were not shown because too many files have changed in this diff Show More