mirror of
https://github.com/ParadiseSS13/Paradise.git
synced 2026-07-09 22:25:46 +01:00
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:
@@ -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" = (
|
||||
|
||||
@@ -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" = (
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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"
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -15,6 +15,7 @@ GLOBAL_LIST_INIT(antag_roles, list(
|
||||
ROLE_MORPH,
|
||||
ROLE_TSPIDER,
|
||||
ROLE_NINJA,
|
||||
ROLE_FLOCK,
|
||||
))
|
||||
|
||||
// Bannable other roles
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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>")
|
||||
|
||||
@@ -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, "")
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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."
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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])
|
||||
|
||||
@@ -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)
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
@@ -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 //
|
||||
//////////////
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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))
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user