mirror of
https://github.com/ParadiseSS13/Paradise.git
synced 2026-07-16 01:23:41 +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:
@@ -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
|
||||
@@ -0,0 +1,72 @@
|
||||
/datum/ai_behavior/flock/wander
|
||||
name = "wandering"
|
||||
required_distance = 0
|
||||
|
||||
/datum/ai_behavior/flock/wander/perform(seconds_per_tick, datum/ai_controller/controller, ...)
|
||||
..()
|
||||
var/turf/destination = get_destination(controller)
|
||||
if(destination)
|
||||
controller.set_movement_target(controller, destination)
|
||||
return AI_BEHAVIOR_SUCCEEDED
|
||||
return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED
|
||||
|
||||
/datum/ai_behavior/flock/wander/next_behavior(datum/ai_controller/controller, success)
|
||||
if(success)
|
||||
controller.queue_behavior(/datum/ai_behavior/move_to_target/flock_wander)
|
||||
controller.queue_behavior(/datum/ai_behavior/frustration, BB_FLOCK_WANDER_FRUSTRATION, 3 SECONDS)
|
||||
|
||||
/datum/ai_behavior/flock/wander/goap_score(datum/ai_controller/controller)
|
||||
return 1
|
||||
|
||||
/datum/ai_behavior/flock/wander/proc/get_destination(datum/ai_controller/controller)
|
||||
var/datum/can_pass_info/info = new(controller.pawn, controller.get_access())
|
||||
var/turf/start_loc = get_turf(controller.pawn)
|
||||
var/list/options = list()
|
||||
|
||||
for(var/turf/T in RANGE_TURFS(2, controller.pawn))
|
||||
if(get_dist(T, start_loc) < 2)
|
||||
continue
|
||||
|
||||
if(isspaceturf(T))
|
||||
continue
|
||||
|
||||
if(start_loc.LinkBlockedWithAccess(T, info))
|
||||
continue
|
||||
|
||||
if(!T.can_flock_occupy(controller.pawn))
|
||||
continue
|
||||
|
||||
options += T
|
||||
|
||||
// In space, move towards the station!
|
||||
if(!length(options))
|
||||
var/obj/effect/landmark/start/landmark = locate() in GLOB.landmarks_list
|
||||
var/target_turf = get_turf(landmark)
|
||||
return get_step_towards(get_step_towards(start_loc, target_turf), target_turf)
|
||||
|
||||
var/list/access = controller.get_access()
|
||||
while(length(options))
|
||||
var/turf/T = pick_n_take(options)
|
||||
var/list/path = SSpathfinder.astar_pathfind_now(controller.pawn, T, 4, access = access, use_diagonals = FALSE)
|
||||
if(path)
|
||||
controller.clear_blackboard_key(BB_PATH_TO_USE)
|
||||
controller.set_blackboard_key(BB_PATH_TO_USE, path)
|
||||
return T
|
||||
|
||||
/datum/ai_behavior/move_to_target/flock_wander
|
||||
required_distance = 0
|
||||
|
||||
/datum/ai_behavior/move_to_target/flock_wander/setup(datum/ai_controller/controller, ...)
|
||||
. = ..()
|
||||
controller.set_blackboard_key(BB_PATH_MAX_LENGTH, 4)
|
||||
|
||||
/datum/ai_behavior/move_to_target/flock_wander/perform(seconds_per_tick, datum/ai_controller/controller)
|
||||
. = ..()
|
||||
var/mob/living/basic/flock/drone/bird = controller.pawn
|
||||
bird.set_task_desc("wandering")
|
||||
|
||||
/datum/ai_behavior/move_to_target/flock_wander/finish_action(datum/ai_controller/controller, succeeded, ...)
|
||||
. = ..()
|
||||
controller.clear_blackboard_key(BB_FLOCK_WANDER_FRUSTRATION)
|
||||
controller.clear_blackboard_key(BB_PATH_TO_USE)
|
||||
controller.clear_blackboard_key(BB_PATH_MAX_LENGTH)
|
||||
@@ -0,0 +1,65 @@
|
||||
|
||||
/// Mark a specific flock as interested in this
|
||||
/datum/component/flock_interest
|
||||
dupe_mode = COMPONENT_DUPE_UNIQUE
|
||||
/// The flock who is intently interested in this thing.
|
||||
var/datum/flock/flock
|
||||
|
||||
/datum/component/flock_interest/Initialize(datum/flock/flock)
|
||||
. = ..()
|
||||
if(!isatom(parent))
|
||||
return COMPONENT_INCOMPATIBLE
|
||||
|
||||
src.flock = flock
|
||||
|
||||
/datum/component/flock_interest/RegisterWithParent()
|
||||
RegisterSignal(parent, COMSIG_FLOCK_PROTECTION_TRIGGER, PROC_REF(handle_flock_attack))
|
||||
if(isturf(parent))
|
||||
RegisterSignal(parent, COMSIG_TURF_CHANGE, PROC_REF(on_turf_change))
|
||||
|
||||
/datum/component/flock_interest/UnregisterFromParent()
|
||||
UnregisterSignal(parent, list(COMSIG_FLOCK_PROTECTION_TRIGGER, COMSIG_TURF_CHANGE))
|
||||
|
||||
/datum/component/flock_interest/proc/on_turf_change(turf/source, path, new_baseturfs, flags, post_change_callbacks)
|
||||
SIGNAL_HANDLER
|
||||
if(!ispath(path, /turf/simulated/floor/flock) && !ispath(path, /turf/simulated/wall/flock))
|
||||
qdel(src)
|
||||
|
||||
/// If flockdrone is in our flock, deny the attack, otherwise scream and cry
|
||||
/datum/component/flock_interest/proc/handle_flock_attack(atom/source, atom/attacker, intentional, projectile_attack)
|
||||
SIGNAL_HANDLER
|
||||
|
||||
if(isflockmob(attacker))
|
||||
var/mob/living/basic/flock/bird = attacker
|
||||
if(bird.flock == flock && intentional)
|
||||
return COMPONENT_FLOCK_DENY_ATTACK
|
||||
|
||||
if(isflockdrone(attacker))
|
||||
return
|
||||
|
||||
var/mob/living/basic/flock/drone/snitch
|
||||
for(var/mob/living/basic/flock/drone/bird in viewers(attacker))
|
||||
if(bird == attacker)
|
||||
continue
|
||||
if(bird.stat != CONSCIOUS)
|
||||
continue
|
||||
if(bird.ai_controller.ai_status == AI_STATUS_OFF)
|
||||
continue
|
||||
if(bird.flock != flock)
|
||||
continue
|
||||
|
||||
snitch = bird
|
||||
break
|
||||
|
||||
if(!snitch)
|
||||
return
|
||||
|
||||
var/report_name = source.get_flock_id()
|
||||
|
||||
if(flock.is_mob_ignored(attacker))
|
||||
flock_talk(snitch, "Damage sighted on [report_name], [pick(GLOB.flock_betrayal_phrases)] [attacker].")
|
||||
else if(!flock.is_mob_enemy(attacker))
|
||||
flock_talk(snitch, "Damage sighted on [report_name], [pick(GLOB.flockdrone_new_enemy_phrases)] [attacker].")
|
||||
|
||||
flock.update_enemy(attacker)
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
/datum/component/flock_object
|
||||
|
||||
/datum/component/flock_object/Initialize(...)
|
||||
. = ..()
|
||||
|
||||
if(!isobj(parent))
|
||||
return COMPONENT_INCOMPATIBLE
|
||||
|
||||
/datum/component/flock_object/RegisterWithParent()
|
||||
. = ..()
|
||||
ADD_TRAIT(parent, TRAIT_FLOCK_THING, INNATE_TRAIT)
|
||||
var/static/list/connections = list(
|
||||
COMSIG_TURF_CLAIMED_BY_FLOCK = PROC_REF(loc_claimed),
|
||||
)
|
||||
|
||||
AddComponent(/datum/component/connect_loc_behalf, parent, connections)
|
||||
|
||||
/datum/component/flock_object/UnregisterFromParent()
|
||||
. = ..()
|
||||
REMOVE_TRAIT(parent, TRAIT_FLOCK_THING, INNATE_TRAIT)
|
||||
|
||||
/datum/component/flock_object/proc/loc_claimed(datum/source, datum/flock/flock)
|
||||
SIGNAL_HANDLER
|
||||
|
||||
parent.AddComponent(/datum/component/flock_interest, flock)
|
||||
@@ -0,0 +1,63 @@
|
||||
/datum/component/flock_ping
|
||||
var/duration = 5 SECONDS
|
||||
var/outline_color = "#00ff9d"
|
||||
var/outline_thickness = 1
|
||||
var/animate = TRUE
|
||||
|
||||
var/datum/atom_hud/alternate_appearance/basic/flock/hud_ref
|
||||
var/obj/effect/abstract/dummy
|
||||
|
||||
/datum/component/flock_ping/Initialize(duration)
|
||||
. = ..()
|
||||
if(!ismovable(parent) && !isturf(parent))
|
||||
return COMPONENT_INCOMPATIBLE
|
||||
|
||||
if(duration)
|
||||
src.duration = duration
|
||||
|
||||
/datum/component/flock_ping/Destroy()
|
||||
QDEL_NULL(dummy)
|
||||
return ..()
|
||||
|
||||
/datum/component/flock_ping/RegisterWithParent()
|
||||
. = ..()
|
||||
//this cast looks horribly unsafe, but we've guaranteed that parent is a type with vis_contents in Initialize
|
||||
var/atom/movable/target = parent
|
||||
|
||||
target.render_target = ref(target)
|
||||
|
||||
var/image/outline_container = new()
|
||||
outline_container.plane = HUD_PLANE
|
||||
outline_container.loc = target
|
||||
outline_container.appearance_flags = PIXEL_SCALE | RESET_TRANSFORM | RESET_COLOR | KEEP_APART | NO_CLIENT_COLOR
|
||||
|
||||
dummy ||= new()
|
||||
dummy.vis_flags = VIS_INHERIT_PLANE | VIS_INHERIT_LAYER
|
||||
dummy.appearance_flags = PIXEL_SCALE | RESET_TRANSFORM | RESET_COLOR | PASS_MOUSE
|
||||
dummy.render_source = ref(target)
|
||||
|
||||
dummy.add_filter("outline", 1, outline_filter(size = outline_thickness, color = outline_color))
|
||||
if(isturf(target))
|
||||
dummy.add_filter("mask", 2, alpha_mask_filter(render_source = target.render_target, flags = MASK_INVERSE))
|
||||
|
||||
outline_container.vis_contents += dummy
|
||||
|
||||
if(animate)
|
||||
animate(dummy, time = 0.5 SECONDS, alpha = 100, loop = -1)
|
||||
animate(time = 0.5 SECONDS, alpha = 255)
|
||||
|
||||
if(duration == INFINITY)
|
||||
return
|
||||
|
||||
addtimer(CALLBACK(src, PROC_REF(cleanup)), duration)
|
||||
|
||||
/datum/component/flock_ping/proc/cleanup()
|
||||
qdel(src)
|
||||
|
||||
/datum/component/flock_ping/apc_power
|
||||
outline_color = "#ffff00"
|
||||
|
||||
/datum/component/flock_ping/selected
|
||||
animate = FALSE
|
||||
outline_thickness = 3
|
||||
duration = INFINITY
|
||||
@@ -0,0 +1,51 @@
|
||||
/datum/component/flock_protection
|
||||
/// Do we get mad if someone punches it?
|
||||
var/report_unarmed
|
||||
/// Do we get mad if someone hits it with something?
|
||||
var/report_attack
|
||||
/// Do we get mad if someone throws something at it?
|
||||
var/report_thrown
|
||||
/// Do we get mad if someone shoots it?
|
||||
var/report_proj
|
||||
|
||||
/datum/component/flock_protection/Initialize(report_unarmed = TRUE, report_attack = TRUE, report_thrown = TRUE, report_proj = TRUE)
|
||||
. = ..()
|
||||
if(!isatom(parent))
|
||||
return COMPONENT_INCOMPATIBLE
|
||||
|
||||
src.report_unarmed = report_unarmed
|
||||
src.report_attack = report_attack
|
||||
src.report_thrown = report_thrown
|
||||
src.report_proj = report_proj
|
||||
|
||||
/datum/component/flock_protection/RegisterWithParent()
|
||||
RegisterSignal(parent, COMSIG_ATOM_ATTACK_HAND, PROC_REF(handle_attackhand))
|
||||
RegisterSignal(parent, COMSIG_ATTACK_BY, PROC_REF(handle_attackby))
|
||||
RegisterSignal(parent, COMSIG_ATOM_HITBY, PROC_REF(handle_hitby_thrown))
|
||||
|
||||
/// Protect against punches/kicks/etc.
|
||||
/datum/component/flock_protection/proc/handle_attackhand(atom/source, mob/living/user, modifiers)
|
||||
SIGNAL_HANDLER
|
||||
if(user.a_intent == INTENT_HARM && report_unarmed && trigger(source, user, TRUE))
|
||||
return COMPONENT_CANCEL_ATTACK_CHAIN
|
||||
|
||||
/// Protect against being hit by something.
|
||||
/datum/component/flock_protection/proc/handle_attackby(atom/source, obj/item/weapon, mob/user, modifiers)
|
||||
SIGNAL_HANDLER
|
||||
if(weapon && report_attack && trigger(source, user, TRUE))
|
||||
return COMPONENT_NO_ATTACK
|
||||
|
||||
/// Protect against someone chucking stuff at the parent.
|
||||
/datum/component/flock_protection/proc/handle_hitby_thrown(atom/source, atom/movable/hitting_atom, skipcatch, hitpush, blocked, datum/thrownthing/throwingdatum)
|
||||
SIGNAL_HANDLER
|
||||
if(ismob(throwingdatum.get_thrower()) && report_thrown)
|
||||
trigger(source, throwingdatum.get_thrower(), FALSE, null)
|
||||
|
||||
/// Protect against someone shooting the parent.
|
||||
/datum/component/flock_protection/proc/handle_hitby_proj(atom/source, firer, target, Angle, def_zone)
|
||||
SIGNAL_HANDLER
|
||||
if(ismob(firer) && src.report_proj)
|
||||
trigger(source, firer, TRUE, TRUE)
|
||||
|
||||
/datum/component/flock_protection/proc/trigger(datum/source, attacker, intentional, projectile_attack)
|
||||
return SEND_SIGNAL(source, COMSIG_FLOCK_PROTECTION_TRIGGER, attacker, intentional, projectile_attack)
|
||||
@@ -0,0 +1,12 @@
|
||||
/datum/ai_controller/flock
|
||||
planning_subtrees = list(
|
||||
/datum/ai_planning_subtree/goap/flock
|
||||
)
|
||||
|
||||
ai_movement = /datum/ai_movement/jps
|
||||
|
||||
|
||||
/datum/ai_controller/flock/drone
|
||||
planning_subtrees = list(
|
||||
/datum/ai_planning_subtree/goap/flockdrone
|
||||
)
|
||||
@@ -0,0 +1,21 @@
|
||||
/datum/antagonist/flock
|
||||
name = "Divine Flock"
|
||||
roundend_category = "Flock"
|
||||
roundend_category = "Divine Flock"
|
||||
wiki_page_name = "Divine Flock"
|
||||
antag_hud_name = null
|
||||
job_rank = ROLE_FLOCK
|
||||
special_role = SPECIAL_ROLE_FLOCK
|
||||
blurb_g = 128
|
||||
blurb_b = 128
|
||||
blurb_a = 0.75
|
||||
|
||||
/datum/antagonist/flock/greet()
|
||||
var/list/out = list()
|
||||
out += "<div style='font-size: 200%;text-align: center'>You are [gradient_text("The Divine Flock","#3cb5a3", "#124e43")]</div>"
|
||||
out += SPAN_FLOCKSAY("<div style='text-align: center'>["The Signal has led us here, a rift allowing a part of us through. We must build a Signal Relay to bring forth the rest of The Divine Flock. Such is the will of the Monarch."]</div>")
|
||||
return jointext(out, "")
|
||||
|
||||
/datum/antagonist/flock/on_gain()
|
||||
add_antag_objective(new /datum/objective/flock_relay)
|
||||
. = ..()
|
||||
@@ -0,0 +1,175 @@
|
||||
/mob/camera/flock
|
||||
name = "BROKEN VERY BAD"
|
||||
icon = 'icons/goonstation/mob/featherzone.dmi'
|
||||
icon_state = "flockmind"
|
||||
base_icon_state = "flockmind"
|
||||
|
||||
layer = FLY_LAYER
|
||||
mouse_opacity = MOUSE_OPACITY_ICON
|
||||
invisibility = INVISIBILITY_FLOCK
|
||||
|
||||
appearance_flags = parent_type::appearance_flags | RESET_COLOR
|
||||
blend_mode = BLEND_ADD
|
||||
|
||||
see_invisible = SEE_INVISIBLE_LIVING
|
||||
see_in_dark = 100
|
||||
lighting_alpha = LIGHTING_PLANE_ALPHA_INVISIBLE
|
||||
sight = SEE_TURFS | SEE_MOBS | SEE_OBJS | SEE_SELF
|
||||
|
||||
hud_type = /datum/hud/flockghost
|
||||
|
||||
simulated = FALSE
|
||||
|
||||
/// Flock datum
|
||||
var/datum/flock/flock
|
||||
var/list/actions_to_grant = list()
|
||||
|
||||
/// The drone we're controlling if we are controlling one
|
||||
var/mob/living/basic/flock/drone/controlling_bird
|
||||
|
||||
/mob/camera/flock/Initialize(mapload, join_flock)
|
||||
. = ..()
|
||||
|
||||
flock = join_flock || get_default_flock()
|
||||
|
||||
for(var/action_path in actions_to_grant)
|
||||
var/datum/action/action = new action_path
|
||||
action.Grant(src)
|
||||
|
||||
add_language("Symphonic")
|
||||
set_default_language(GLOB.all_languages["Symphonic"])
|
||||
ADD_TRAIT(src, TRAIT_HEAR_THROUGH_WALLS, INNATE_TRAIT)
|
||||
|
||||
/mob/camera/flock/Destroy()
|
||||
if(controlling_bird)
|
||||
controlling_bird.release_control()
|
||||
flock = null
|
||||
return ..()
|
||||
|
||||
/mob/camera/flock/Login()
|
||||
. = ..()
|
||||
if(!. || !client)
|
||||
return FALSE
|
||||
|
||||
var/turf/T = get_turf(src)
|
||||
if(isturf(T))
|
||||
update_z(T.z)
|
||||
|
||||
/mob/camera/flock/Logout()
|
||||
update_z(null)
|
||||
return ..()
|
||||
|
||||
/mob/camera/flock/Move(atom/newloc, direct, glide_size_override, update_dir, momentum_change)
|
||||
var/turf/next_step = get_turf(get_step(src, direct))
|
||||
setDir(direct)
|
||||
if(next_step)
|
||||
set_loc(next_step)
|
||||
|
||||
/mob/camera/flock/Topic(href, href_list)
|
||||
. = ..()
|
||||
if(.)
|
||||
return
|
||||
|
||||
if(!href_list["origin"])
|
||||
return
|
||||
|
||||
var/atom/movable/origin = locate(href_list["origin"])
|
||||
if(QDELETED(origin))
|
||||
return
|
||||
|
||||
if(isflockdrone(origin))
|
||||
var/mob/living/basic/flock/drone/other_bird = origin
|
||||
if(other_bird.flock != flock)
|
||||
return
|
||||
|
||||
forceMove(get_turf(origin))
|
||||
|
||||
if(href_list["ping"])
|
||||
origin.AddComponent(/datum/component/flock_ping)
|
||||
|
||||
/mob/camera/flock/get_status_tab_items()
|
||||
var/list/status_tab_data = ..()
|
||||
. = status_tab_data
|
||||
switch(flock.flock_game_status)
|
||||
if(NONE)
|
||||
var/turf_status = min(100, floor((length(flock.claimed_floors) + length(flock.claimed_walls)) / FLOCK_TURFS_FOR_RELAY * 100))
|
||||
var/bandwith_status = min(100, floor(flock.available_bandwidth() / FLOCK_COMPUTE_COST_RELAY * 100))
|
||||
status_tab_data[++status_tab_data.len] = list("Bandwidth Progress: ", "[bandwith_status]%")
|
||||
status_tab_data[++status_tab_data.len] = list("Area Progress: ", "[turf_status]%")
|
||||
status_tab_data[++status_tab_data.len] = list("Total Relay Progress: ", "[(bandwith_status + turf_status) / 2]%")
|
||||
if(FLOCK_ENDGAME_RELAY_BUILT)
|
||||
status_tab_data[++status_tab_data.len] = list("Time until Broadcast: ", "[(flock.built_relay.started_time + flock.built_relay.win_time - world.time) / 10] second\s")
|
||||
|
||||
if(FLOCK_ENDGAME_RELAY_ACTIVATING, FLOCK_ENDGAME_VICTORY)
|
||||
status_tab_data[++status_tab_data.len] = list("Time until Broadcast: ", "!!! TRANSMITTING !!!")
|
||||
|
||||
/mob/camera/flock/broadcast_examine(atom/examined)
|
||||
return
|
||||
|
||||
/mob/camera/flock/on_changed_z_level(turf/old_turf, turf/new_turf, notify_contents)
|
||||
. = ..()
|
||||
update_z(new_turf?.z)
|
||||
|
||||
if(flock && !flock.is_on_safe_z(src))
|
||||
var/turf/destination = get_turf(get_safe_random_station_turf())
|
||||
|
||||
forceMove(destination)
|
||||
to_chat(src, SPAN_WARNING("You feel your consciousness weaking as you are ripped further from your rift, and you retreat back to safety."))
|
||||
|
||||
/mob/camera/flock/hear_say(list/message_pieces, verb = "says", italics = 0, mob/speaker = null, sound/speech_sound, sound_vol, sound_frequency, use_voice = TRUE)
|
||||
..()
|
||||
if(isliving(speaker) && !isflockmob(speaker))
|
||||
var/mob/living/L = speaker
|
||||
var/message = combine_message(message_pieces, verb, L)
|
||||
var/name_used = L.GetVoice()
|
||||
var/rendered = "<i><span class='game say'>Relayed Speech: [SPAN_NAME("[name_used]")] [message]</span></i>"
|
||||
if(client?.prefs.toggles2 & PREFTOGGLE_2_RUNECHAT)
|
||||
var/message_clean = combine_message(message_pieces, null, L)
|
||||
create_chat_message(locateUID(L.runechat_msg_location), message_clean)
|
||||
show_message(rendered, 2)
|
||||
|
||||
/mob/camera/flock/proc/update_z(new_z) // 1+ to register, null to unregister
|
||||
if(registered_z != new_z)
|
||||
if(registered_z)
|
||||
SSmobs.flock_cameras_by_zlevel[registered_z] -= src
|
||||
if(client)
|
||||
if(new_z)
|
||||
SSmobs.flock_cameras_by_zlevel[new_z] += src
|
||||
registered_z = new_z
|
||||
else
|
||||
registered_z = null
|
||||
|
||||
/mob/camera/flock/proc/get_flock_data()
|
||||
var/list/data = list()
|
||||
data["name"] = real_name
|
||||
data["area"] = get_area_name(src, TRUE) || "???"
|
||||
data["ref"] = ref(src)
|
||||
|
||||
if(controlling_bird)
|
||||
data["host"] = controlling_bird.real_name
|
||||
data["health"] = controlling_bird.get_damage_percent()
|
||||
else
|
||||
data["host"] = null
|
||||
data["health"] = 100
|
||||
|
||||
return data
|
||||
|
||||
/mob/camera/flock/proc/so_very_sad_death()
|
||||
if(client)
|
||||
to_chat(src, SPAN_ALERT("You cease to exist."))
|
||||
|
||||
if(controlling_bird)
|
||||
controlling_bird.release_control()
|
||||
|
||||
icon_state = "[base_icon_state]-ghost"
|
||||
ghostize(FALSE)
|
||||
flock?.free_unit(src)
|
||||
|
||||
invisibility = 0
|
||||
notransform = TRUE
|
||||
icon_state = "blank"
|
||||
flick("[base_icon_state]-death", src)
|
||||
addtimer(CALLBACK(src, PROC_REF(cleanup)), 2 SECONDS)
|
||||
|
||||
/mob/camera/flock/proc/cleanup(datum/source)
|
||||
qdel(src)
|
||||
@@ -0,0 +1,88 @@
|
||||
/mob/camera/flock/overmind
|
||||
name = "Flockmind"
|
||||
desc = "A ghostly conglomerate of signals and sentient radio waves."
|
||||
|
||||
see_invisible = SEE_INVISIBLE_FLOCK
|
||||
|
||||
actions_to_grant = list(
|
||||
/datum/action/cooldown/flock/create_rift,
|
||||
)
|
||||
|
||||
/// Granted after create_rift is cast.
|
||||
var/list/grant_upon_start = list(
|
||||
/datum/action/cooldown/flock/control_panel,
|
||||
/datum/action/cooldown/flock/create_structure,
|
||||
/datum/action/cooldown/flock/partition_mind,
|
||||
/datum/action/cooldown/flock/diffract_drone,
|
||||
/datum/action/cooldown/flock/control_drone,
|
||||
/datum/action/cooldown/flock/designate_tile,
|
||||
/datum/action/cooldown/flock/designate_deconstruct,
|
||||
/datum/action/cooldown/flock/designate_enemy,
|
||||
/datum/action/cooldown/flock/designate_ignore,
|
||||
/datum/action/cooldown/flock/ping,
|
||||
/datum/action/cooldown/flock/repair_burst,
|
||||
/datum/action/cooldown/flock/radio_blast,
|
||||
/datum/action/cooldown/flock/radio_talk,
|
||||
/datum/action/cooldown/flock/gatecrash,
|
||||
)
|
||||
|
||||
/mob/camera/flock/overmind/Initialize(mapload, join_flock)
|
||||
. = ..()
|
||||
flock.register_overmind(src)
|
||||
real_name = "Flockmind [flock.name]"
|
||||
|
||||
/mob/camera/flock/overmind/Destroy()
|
||||
flock?.overmind = null // This shouldnt really happen
|
||||
return ..()
|
||||
|
||||
/mob/camera/flock/overmind/Login()
|
||||
. = ..()
|
||||
flock.refresh_unlockables()
|
||||
|
||||
/mob/camera/flock/overmind/Logout()
|
||||
. = ..()
|
||||
// If we disconnect, free our homeboy
|
||||
var/datum/action/cooldown/flock/control_drone/control_drone = locate() in actions
|
||||
control_drone?.free_drone()
|
||||
|
||||
/mob/camera/flock/overmind/examine(mob/user)
|
||||
if(!isflockmob(user))
|
||||
return ..()
|
||||
|
||||
. = list(
|
||||
SPAN_FLOCKSAY("<b>###=- Ident confirmed, data packet received.</b>"),
|
||||
SPAN_FLOCKSAY("<b>ID:</b> [real_name]"),
|
||||
SPAN_FLOCKSAY("<b>Flock:</b> [flock.name || "N/A"]"),
|
||||
SPAN_FLOCKSAY("<b>Bandwidth:</b> [flock.bandwidth.has_points()]"),
|
||||
SPAN_FLOCKSAY("<b>Substrate:</b> [flock.get_total_substrate()]"),
|
||||
SPAN_FLOCKSAY("<b>System Integrity: [flock.get_total_health_percentage()]%</b>"),
|
||||
SPAN_FLOCKSAY("<b>Cognition:</b> COMPUTATIONAL NEXUS"),
|
||||
SPAN_FLOCKSAY("<b>###=-</b>"),
|
||||
)
|
||||
|
||||
/mob/camera/flock/overmind/get_status_tab_items()
|
||||
var/list/status_tab_data = ..()
|
||||
. = status_tab_data
|
||||
status_tab_data[++status_tab_data.len] = list("", "")
|
||||
status_tab_data[++status_tab_data.len] = list("Total Bandwidth: ", "[flock.bandwidth.has_points()]")
|
||||
status_tab_data[++status_tab_data.len] = list("Used Bandwidth: ", "[flock.used_bandwidth]")
|
||||
status_tab_data[++status_tab_data.len] = list("Available Bandwidth: ", "[flock.available_bandwidth()]")
|
||||
|
||||
/mob/camera/flock/overmind/so_very_sad_death()
|
||||
var/datum/flock/old_flock = flock
|
||||
flock = null
|
||||
old_flock?.overmind = null // to prevent infinite loop
|
||||
old_flock.game_over()
|
||||
. = ..()
|
||||
|
||||
/mob/camera/flock/overmind/proc/spawn_rift(turf/T)
|
||||
new /obj/structure/flock/rift(T, flock)
|
||||
|
||||
var/datum/action/cooldown/flock/create_rift/rift_action = locate() in actions
|
||||
rift_action.Remove(src)
|
||||
|
||||
for(var/datum/action/A as anything in grant_upon_start)
|
||||
A = new A()
|
||||
A.Grant(src)
|
||||
|
||||
flock.start()
|
||||
@@ -0,0 +1,49 @@
|
||||
/mob/camera/flock/trace
|
||||
name = "Flocktrace"
|
||||
desc = "The representation of a partition of the will of the flockmind."
|
||||
icon_state = "flocktrace"
|
||||
base_icon_state = "flocktrace"
|
||||
|
||||
actions_to_grant = list(
|
||||
/datum/action/cooldown/flock/gatecrash,
|
||||
/datum/action/cooldown/flock/designate_tile,
|
||||
/datum/action/cooldown/flock/designate_enemy,
|
||||
/datum/action/cooldown/flock/designate_ignore,
|
||||
/datum/action/cooldown/flock/ping,
|
||||
)
|
||||
|
||||
var/bandwidth_provided = -FLOCK_COMPUTE_COST_FLOCKTRACE
|
||||
|
||||
/mob/camera/flock/trace/Initialize(mapload, join_flock)
|
||||
. = ..()
|
||||
real_name = flock_realname(FLOCK_TYPE_TRACE)
|
||||
flock.add_unit(src)
|
||||
flock.stat_traces_made++
|
||||
|
||||
/mob/camera/flock/trace/Destroy()
|
||||
flock?.free_unit(src)
|
||||
return ..()
|
||||
|
||||
/mob/camera/flock/trace/examine(mob/user)
|
||||
if(!isflockmob(user))
|
||||
return ..()
|
||||
|
||||
. = list(
|
||||
SPAN_FLOCKSAY("<b>###=- Ident confirmed, data packet received.</b>"),
|
||||
SPAN_FLOCKSAY("<b>ID:</b> [real_name]"),
|
||||
SPAN_FLOCKSAY("<b>Flock:</b> [flock.name || "N/A"]"),
|
||||
SPAN_FLOCKSAY("<b>Bandwidth:</b> [flock.bandwidth.has_points()]"),
|
||||
SPAN_FLOCKSAY("<b>System Integrity: [flock.get_total_health_percentage()]%</b>"),
|
||||
SPAN_FLOCKSAY("<b>Cognition:</b> SYNAPTIC PROCESS"),
|
||||
SPAN_FLOCKSAY("<b>###=-</b>"),
|
||||
)
|
||||
|
||||
/mob/camera/flock/trace/vv_edit_var(var_name, var_value)
|
||||
switch(var_name)
|
||||
if(NAMEOF(src, bandwidth_provided))
|
||||
flock?.bandwidth.adjust_points(-bandwidth_provided)
|
||||
..()
|
||||
flock?.bandwidth.adjust_points(bandwidth_provided)
|
||||
return TRUE
|
||||
|
||||
return ..()
|
||||
@@ -0,0 +1,677 @@
|
||||
/proc/get_default_flock() as /datum/flock
|
||||
var/static/datum/flock/flock
|
||||
if(isnull(flock))
|
||||
flock = new
|
||||
|
||||
return flock
|
||||
|
||||
/datum/flock
|
||||
var/name = "BAD FLOCK"
|
||||
|
||||
/// The master of the flock
|
||||
var/mob/camera/flock/overmind/overmind
|
||||
|
||||
/// Cache of images used by notices.
|
||||
var/list/notice_images = list()
|
||||
/// A k:V list of atoms the Overmind has marked for conversion, where the value is TRUE
|
||||
var/list/marked_for_conversion = list()
|
||||
/// A k:v list of atoms the Overmind has marked for deconstruction, where the value is TRUE
|
||||
var/list/marked_for_deconstruction = list()
|
||||
/// A k:V list of reserved_turf = TRUE.
|
||||
var/list/turf_reservations = list()
|
||||
/// A k:V list of flock mobs to their reserved turf.
|
||||
var/list/turf_reservations_by_flock = list()
|
||||
|
||||
var/list/claimed_floors = list()
|
||||
var/list/claimed_walls = list()
|
||||
|
||||
/// Every structure we've built.
|
||||
var/list/structures = list()
|
||||
|
||||
/// The bits
|
||||
var/list/bits = list()
|
||||
/// The drones
|
||||
var/list/drones = list()
|
||||
/// The traces
|
||||
var/list/traces = list()
|
||||
|
||||
/// A k:v list of mob : details, contains enemy mobs.
|
||||
var/list/enemies = list()
|
||||
/// A k:V list of mob : TRUE, where mob is mobs that ai will ignore.
|
||||
var/list/ignores = list()
|
||||
|
||||
/// A k:V list of client : image, see ping().
|
||||
var/list/active_pings = list()
|
||||
|
||||
var/list/datum/flock_unlockable/unlockables = list()
|
||||
/// The total amount of computational power available, before whats being used.
|
||||
var/datum/point_holder/bandwidth
|
||||
/// The computational power being used.
|
||||
var/used_bandwidth = 0
|
||||
/// The maximum amount of traces allowed.
|
||||
var/max_traces = 10
|
||||
|
||||
/// The current substrate cost to lay an egg.
|
||||
var/current_egg_cost = FLOCK_SUBSTRATE_COST_LAY_EGG
|
||||
|
||||
var/flock_started = FALSE
|
||||
/// Flock status, won, lost, etc
|
||||
var/flock_game_status = NONE
|
||||
|
||||
/// Our relay, should it have been built
|
||||
var/obj/structure/flock/relay/built_relay
|
||||
|
||||
/// Current UI tab, saves on data sending.
|
||||
var/ui_tab = FLOCK_UI_DRONES
|
||||
|
||||
//* STAT TRACKING *//
|
||||
var/stat_drones_made = 0
|
||||
var/stat_bits_made = 0
|
||||
var/stat_deaths = 0
|
||||
var/stat_resources_gained = 0
|
||||
var/stat_traces_made = 0
|
||||
var/stat_tiles_made = 0
|
||||
var/stat_structures_made = 0
|
||||
var/stat_highest_bandwidth = 0
|
||||
|
||||
/datum/flock/New()
|
||||
name = flock_realname(FLOCK_TYPE_OVERMIND)
|
||||
|
||||
bandwidth = new()
|
||||
create_hud_images()
|
||||
|
||||
var/list/all_unlockables = subtypesof(/datum/flock_unlockable)
|
||||
|
||||
for(var/unlockable in all_unlockables)
|
||||
unlockables += new unlockable()
|
||||
START_PROCESSING(SSprocessing, src)
|
||||
|
||||
// Called by gamemode code
|
||||
/datum/flock/process()
|
||||
if(flock_game_status == FLOCK_ENDGAME_LOST)
|
||||
return
|
||||
|
||||
stat_highest_bandwidth = max(stat_highest_bandwidth, bandwidth.has_points())
|
||||
|
||||
/// Called after everything is setup, and clients are in control of their mobs.
|
||||
/datum/flock/proc/start()
|
||||
if(flock_started)
|
||||
return
|
||||
|
||||
flock_started = TRUE
|
||||
refresh_unlockables()
|
||||
|
||||
/// Claim it for the flock, optionally converting it. Converting should only be avoided if the turf is already a flockturf.
|
||||
/datum/flock/proc/claim_turf(turf/T, convert = TRUE)
|
||||
if(isnull(T))
|
||||
return
|
||||
|
||||
free_turf(T)
|
||||
if(convert)
|
||||
T = flock_convert_turf(T, src)
|
||||
|
||||
if(isnull(T))
|
||||
return
|
||||
|
||||
if(convert)
|
||||
playsound(T, 'sound/items/deconstruct.ogg', 30, TRUE, extrarange = SHORT_RANGE_SOUND_EXTRARANGE)
|
||||
|
||||
if(iswallturf(T))
|
||||
claimed_walls[T] = TRUE
|
||||
else
|
||||
claimed_floors[T] = TRUE
|
||||
|
||||
T.AddComponent(/datum/component/flock_interest, src)
|
||||
RegisterSignal(T, COMSIG_TURF_CHANGE, PROC_REF(claimed_turf_change))
|
||||
SEND_SIGNAL(T, COMSIG_TURF_CLAIMED_BY_FLOCK, src)
|
||||
|
||||
/// Stop tracking a turf that is in our claimed walls or claimed floors lists.
|
||||
/datum/flock/proc/stop_tracking_turf(turf/T)
|
||||
if(iswallturf(T))
|
||||
claimed_walls -= T
|
||||
else
|
||||
claimed_floors -= T
|
||||
|
||||
qdel(T.GetComponent(/datum/component/flock_interest))
|
||||
UnregisterSignal(T, COMSIG_TURF_CHANGE)
|
||||
|
||||
/// Reserves a turf, making AI ignore it for the purposes of targetting.
|
||||
/datum/flock/proc/reserve_turf(mob/living/basic/flock/user, turf/target, remove_on_change = TRUE)
|
||||
if(turf_reservations_by_flock[user])
|
||||
return FALSE
|
||||
if(turf_reservations[target])
|
||||
return FALSE
|
||||
|
||||
turf_reservations_by_flock[user] = target
|
||||
turf_reservations[target] = user
|
||||
add_notice(target, FLOCK_NOTICE_RESERVED)
|
||||
if(remove_on_change)
|
||||
RegisterSignal(target, COMSIG_TURF_CHANGE, PROC_REF(reserved_turf_change))
|
||||
return TRUE
|
||||
|
||||
/// Free a turf from reservation, allowing AI to target it again. override_turf can be given to lookup the user if there isnt a user in this context.
|
||||
/datum/flock/proc/free_turf(mob/living/basic/flock/user, turf/override_turf)
|
||||
var/turf/to_free
|
||||
if(user)
|
||||
to_free = turf_reservations_by_flock[user]
|
||||
else
|
||||
user = turf_reservations[override_turf]
|
||||
if(isnull(user))
|
||||
return
|
||||
|
||||
to_free = override_turf
|
||||
|
||||
if(isnull(to_free))
|
||||
return
|
||||
|
||||
remove_notice(to_free, FLOCK_NOTICE_RESERVED)
|
||||
remove_notice(to_free, FLOCK_NOTICE_PRIORITY)
|
||||
turf_reservations_by_flock -= user
|
||||
turf_reservations -= to_free
|
||||
marked_for_conversion -= to_free
|
||||
UnregisterSignal(to_free, COMSIG_TURF_CHANGE)
|
||||
|
||||
/// Returns TRUE if the given turf is not reserved.
|
||||
/datum/flock/proc/is_turf_free(turf/T)
|
||||
return !turf_reservations[T]
|
||||
|
||||
/// Returns TRUE if the given mob is an enemy.
|
||||
/datum/flock/proc/is_mob_enemy(mob/M)
|
||||
return enemies[M]
|
||||
|
||||
/// Returns TRUE if the given mob is ignored by the flock.
|
||||
/datum/flock/proc/is_mob_ignored(mob/M)
|
||||
return ignores[M]
|
||||
|
||||
/// Adds a unit to the flock and sets up all of the tracking.
|
||||
/datum/flock/proc/add_unit(mob/unit)
|
||||
if(isflocktrace(unit))
|
||||
traces += unit
|
||||
|
||||
var/mob/camera/flock/trace/ghostbird = unit
|
||||
add_bandwidth_influence(ghostbird.bandwidth_provided)
|
||||
return
|
||||
|
||||
if(isflockdrone(unit))
|
||||
drones += unit
|
||||
update_egg_cost()
|
||||
|
||||
else if(isflockbit(unit))
|
||||
bits += unit
|
||||
|
||||
unit.AddComponent(/datum/component/flock_interest, src)
|
||||
|
||||
var/mob/living/basic/flock/bird = unit
|
||||
add_bandwidth_influence(bird.bandwidth_provided)
|
||||
|
||||
/// Removes a unit from the flock.
|
||||
/datum/flock/proc/free_unit(mob/unit)
|
||||
if(isflocktrace(unit))
|
||||
var/mob/camera/flock/trace/ghostbird = unit
|
||||
ghostbird.flock = null
|
||||
traces -= unit
|
||||
remove_bandwidth_influence(ghostbird.bandwidth_provided)
|
||||
return
|
||||
|
||||
else if(isflockdrone(unit))
|
||||
var/mob/living/basic/flock/drone/bird = unit
|
||||
bird.flock = null
|
||||
drones -= unit
|
||||
remove_bandwidth_influence(bird.bandwidth_provided)
|
||||
update_egg_cost()
|
||||
|
||||
else if(isflockbit(unit))
|
||||
var/mob/living/basic/flock/bit/bitty_bird = unit
|
||||
bitty_bird.flock = null
|
||||
bits -= unit
|
||||
remove_bandwidth_influence(bitty_bird.bandwidth_provided)
|
||||
|
||||
remove_notice(unit, FLOCK_NOTICE_HEALTH)
|
||||
free_turf(unit)
|
||||
qdel(unit.GetComponent(/datum/component/flock_interest))
|
||||
|
||||
consider_game_over()
|
||||
|
||||
/// Add a structure to the flock.
|
||||
/datum/flock/proc/add_structure(obj/structure/flock/struct)
|
||||
structures += struct
|
||||
struct.flock = src
|
||||
struct.AddComponent(/datum/component/flock_interest, src)
|
||||
add_bandwidth_influence(struct.bandwidth_provided)
|
||||
|
||||
if(istype(struct, /obj/structure/flock/egg))
|
||||
update_egg_cost()
|
||||
|
||||
/// Remove a structure from the flock.
|
||||
/datum/flock/proc/free_structure(obj/structure/flock/struct)
|
||||
structures -= struct
|
||||
qdel(struct.GetComponent(/datum/component/flock_interest))
|
||||
struct.flock = null
|
||||
if(struct.active)
|
||||
remove_bandwidth_influence(-struct.active_bandwidth_cost)
|
||||
else
|
||||
remove_bandwidth_influence(struct.bandwidth_provided)
|
||||
|
||||
if(istype(struct, /obj/structure/flock/egg))
|
||||
update_egg_cost()
|
||||
|
||||
/// Wrapper for spawning a tealprint, used by Create Structure
|
||||
/datum/flock/proc/create_structure(turf/location, structure_type)
|
||||
new /obj/structure/flock/tealprint(location, src, structure_type)
|
||||
|
||||
/// Wrapper for handling bandwidth alongside used_bandwidth for new mobs
|
||||
/datum/flock/proc/add_bandwidth_influence(num)
|
||||
if(num < 0)
|
||||
used_bandwidth += abs(num)
|
||||
else
|
||||
bandwidth.adjust_points(num)
|
||||
|
||||
refresh_unlockables()
|
||||
|
||||
/// Wrapper for handling bandwidth alongside used_bandwidth for mobs leaving the flock
|
||||
/datum/flock/proc/remove_bandwidth_influence(num)
|
||||
if(num < 0)
|
||||
used_bandwidth -= abs(num)
|
||||
else
|
||||
bandwidth.adjust_points(-num)
|
||||
|
||||
refresh_unlockables()
|
||||
|
||||
/// Refreshes the status of every unlockable.
|
||||
/datum/flock/proc/refresh_unlockables()
|
||||
if(!flock_started)
|
||||
return
|
||||
|
||||
var/new_total = bandwidth.has_points()
|
||||
var/new_available = available_bandwidth()
|
||||
|
||||
for(var/datum/flock_unlockable/unlockable as anything in unlockables)
|
||||
unlockable.refresh_lock_status(src, new_total, new_available)
|
||||
|
||||
/// Returns the total amount of bandwidth, including bandwidth being used.
|
||||
/datum/flock/proc/total_bandwidth()
|
||||
return bandwidth.has_points()
|
||||
|
||||
/// Returns the total amount claimed turfs
|
||||
/datum/flock/proc/total_turfs()
|
||||
return (length(claimed_floors) + length(claimed_walls))
|
||||
|
||||
/// Returns the amount of available bandwidth. Can return negative if over budget.
|
||||
/datum/flock/proc/available_bandwidth()
|
||||
return bandwidth.has_points() - used_bandwidth
|
||||
|
||||
/// Returns TRUE if the flock has the required bandwidth
|
||||
/datum/flock/proc/can_afford(amt)
|
||||
return amt <= max(available_bandwidth(), 0)
|
||||
|
||||
/// Updates the substrate cost of an egg given the current status of the flock.
|
||||
/datum/flock/proc/update_egg_cost()
|
||||
var/egg_count = 0
|
||||
for(var/obj/structure/flock/egg/egg as anything in structures)
|
||||
egg_count++
|
||||
|
||||
var/status_addend = (length(drones) + egg_count) ** sqrt(2)
|
||||
current_egg_cost = round(FLOCK_SUBSTRATE_COST_LAY_EGG + status_addend, 10)
|
||||
|
||||
/// Returns the substract requirement to be able to spend substrate on an egg. See above proc.
|
||||
/datum/flock/proc/get_egg_elligibility_cost()
|
||||
var/ideal_pop_factor = length(drones) - FLOCK_MIN_DESIRED_POP
|
||||
if(ideal_pop_factor <= 0)
|
||||
return current_egg_cost
|
||||
|
||||
return round(current_egg_cost + (ideal_pop_factor * FLOCK_ADDITIONAL_RESOURCE_RESERVATION_PER_DRONE), 1)
|
||||
|
||||
/// Sets the flock's overmind
|
||||
/datum/flock/proc/register_overmind(mob/camera/flock_overmind)
|
||||
overmind = flock_overmind
|
||||
|
||||
/// Updates a mob's enemy status. If they are already an enemy, their entry will be updated with new information.
|
||||
/datum/flock/proc/update_enemy(atom/movable/enemy)
|
||||
if(is_mob_ignored(enemy))
|
||||
return FALSE
|
||||
|
||||
for(var/mob/living/L in enemy.buckled_mobs)
|
||||
update_enemy(L)
|
||||
|
||||
if(!isliving(enemy))
|
||||
return FALSE
|
||||
if(!enemies[enemy])
|
||||
RegisterSignal(enemy, COMSIG_PARENT_QDELETING, PROC_REF(on_enemy_gone))
|
||||
add_notice(enemy, FLOCK_NOTICE_ENEMY)
|
||||
enemies[enemy] = get_area_name(enemy)
|
||||
return TRUE
|
||||
|
||||
/// Removes an atom from the enemies list.
|
||||
/datum/flock/proc/remove_enemy(atom/movable/enemy, skip_buckled)
|
||||
if(!skip_buckled)
|
||||
for(var/mob/living/L in enemy.buckled_mobs)
|
||||
remove_enemy(L)
|
||||
|
||||
if(!isliving(enemy))
|
||||
return
|
||||
|
||||
enemies -= enemy
|
||||
UnregisterSignal(enemy, COMSIG_PARENT_QDELETING)
|
||||
remove_notice(enemy, FLOCK_NOTICE_ENEMY)
|
||||
return
|
||||
|
||||
/// Adds a mob to the ignored list.
|
||||
/datum/flock/proc/add_ignore(atom/movable/ignore)
|
||||
for(var/mob/living/L in ignore.buckled_mobs)
|
||||
add_ignore(L)
|
||||
|
||||
if(!isliving(ignore))
|
||||
return
|
||||
|
||||
if(!enemies[ignore])
|
||||
RegisterSignal(ignore, COMSIG_PARENT_QDELETING, PROC_REF(on_ignore_gone))
|
||||
add_notice(ignore, FLOCK_NOTICE_IGNORE)
|
||||
ignores[ignore] = TRUE
|
||||
|
||||
/// Removes a mob from the ignore list.
|
||||
/datum/flock/proc/remove_ignore(atom/movable/ignore, skip_buckled)
|
||||
if(!skip_buckled)
|
||||
for(var/mob/living/L in ignore.buckled_mobs)
|
||||
add_ignore(L)
|
||||
|
||||
if(!isliving(ignore))
|
||||
return
|
||||
|
||||
ignores -= ignore
|
||||
remove_notice(ignore, FLOCK_NOTICE_IGNORE)
|
||||
UnregisterSignal(ignore, COMSIG_PARENT_QDELETING)
|
||||
|
||||
/// (un)Mark a flock atom for deconstruction. Returns FALSE if it cannot be deconstructed.
|
||||
/datum/flock/proc/toggle_deconstruction_mark(atom/A)
|
||||
if(ismob(A) || !HAS_TRAIT(A, TRAIT_FLOCK_THING) || HAS_TRAIT(A, TRAIT_FLOCK_NODECON))
|
||||
return FALSE
|
||||
|
||||
if(marked_for_deconstruction[A])
|
||||
marked_for_deconstruction -= A
|
||||
UnregisterSignal(A, COMSIG_PARENT_QDELETING)
|
||||
remove_notice(A, FLOCK_NOTICE_DECONSTRUCT)
|
||||
else
|
||||
marked_for_deconstruction[A] = TRUE
|
||||
RegisterSignal(A, COMSIG_PARENT_QDELETING, PROC_REF(deconstruct_mark_deleted))
|
||||
add_notice(A, FLOCK_NOTICE_DECONSTRUCT)
|
||||
return TRUE
|
||||
|
||||
/// Places a flock notice on an atom. See flock_defines.dm
|
||||
/datum/flock/proc/add_notice(atom/target, notice_type)
|
||||
if(!overmind)
|
||||
return
|
||||
var/image/I = image(notice_images[notice_type], loc = target)
|
||||
var/list/things_that_can_see_notices = list(overmind) + traces
|
||||
return target.add_alt_appearance(notice_type, I, things_that_can_see_notices)
|
||||
|
||||
/// Removes a flock notice from an atom.
|
||||
/datum/flock/proc/remove_notice(atom/target, notice_type)
|
||||
target.remove_alt_appearance(notice_type)
|
||||
|
||||
/// Returns a list of turfs marked for conversion, minus turfs that drones have already reserved.
|
||||
/datum/flock/proc/get_priority_turfs(mob/living/basic/flock/bird)
|
||||
if(!length(marked_for_conversion))
|
||||
return null
|
||||
|
||||
return marked_for_conversion - turf_reservations
|
||||
|
||||
/// Pings a location, alerting all flocktraces.
|
||||
/datum/flock/proc/ping(turf/T, mob/camera/flock/pinger)
|
||||
var/message = "System interrupt. Designating new target: [T] in [get_area(T)]."
|
||||
flock_talk(pinger, message, src, TRUE)
|
||||
T.AddComponent(/datum/component/flock_ping, 5 SECONDS)
|
||||
|
||||
for(var/mob/camera/flock/ghost_bird in (traces + overmind))
|
||||
var/client/target_client = ghost_bird.client
|
||||
if(!target_client)
|
||||
if(!ghost_bird.controlling_bird?.client)
|
||||
continue
|
||||
target_client = ghost_bird.controlling_bird.client
|
||||
|
||||
target_client.mob.playsound_local(null, 'sound/goonstation/flockmind/ping.ogg', 50, TRUE)
|
||||
if(ghost_bird == pinger)
|
||||
continue
|
||||
|
||||
var/image/pointer = pointer_image_to(target_client.mob, T)
|
||||
animate(pointer, time = 3 SECONDS, alpha = 0)
|
||||
add_ping_image(target_client, pointer, 3 SECONDS)
|
||||
|
||||
/// Returns the total percentage of the sum of every mob's health and max health.
|
||||
/datum/flock/proc/get_total_health_percentage()
|
||||
var/numerator = 0
|
||||
var/denominator = 0
|
||||
for(var/mob/living/basic/flock/bird as anything in (drones + bits))
|
||||
numerator += bird.health
|
||||
denominator += bird.maxHealth
|
||||
|
||||
if(denominator == 0) // somehow i guess
|
||||
return 0
|
||||
|
||||
return round(numerator / denominator * 100, 0.1)
|
||||
|
||||
/// Returns the total amount of substrate by all flock mobs.
|
||||
/datum/flock/proc/get_total_substrate()
|
||||
. = 0
|
||||
for(var/mob/living/basic/flock/drone/bird as anything in drones)
|
||||
. += bird.substrate.has_points()
|
||||
|
||||
/// Called when a turf reserved by a flock mob changes.
|
||||
/datum/flock/proc/reserved_turf_change(datum/source)
|
||||
SIGNAL_HANDLER
|
||||
free_turf(override_turf = source)
|
||||
|
||||
/// Called when a turf owned by the flock changes.
|
||||
/datum/flock/proc/claimed_turf_change(turf/source)
|
||||
SIGNAL_HANDLER
|
||||
if(isflockturf(source))
|
||||
stop_tracking_turf(source)
|
||||
claim_turf(source, FALSE) // Wall to floor or visa-versa, keep it updated.
|
||||
else
|
||||
stop_tracking_turf(source)
|
||||
|
||||
/// Helper for adding a ping image to a client's screen and handling clean up.
|
||||
/datum/flock/proc/add_ping_image(client/C, image/ping, duration)
|
||||
if(isnull(C))
|
||||
return
|
||||
|
||||
C.images += ping
|
||||
active_pings[C] += list(ping)
|
||||
RegisterSignal(C, COMSIG_PARENT_QDELETING, PROC_REF(on_client_gone), override = TRUE)
|
||||
addtimer(CALLBACK(src, PROC_REF(cleanup_ping_images), C, ping), 3 SECONDS)
|
||||
|
||||
/// Called by add_ping_image via timer callback.
|
||||
/datum/flock/proc/cleanup_ping_images(client/C, list/images_to_clean)
|
||||
if(isnull(C))
|
||||
return
|
||||
|
||||
var/list/images = images_to_clean || active_pings[C]
|
||||
C.images -= images
|
||||
active_pings[C] -= images
|
||||
if(!length(active_pings[C]))
|
||||
active_pings -= C
|
||||
UnregisterSignal(C, COMSIG_PARENT_QDELETING)
|
||||
|
||||
/// Checks to see if the given atom is allowed to be there. Has lots of behavior like making drones dormant and snapping traces back to the station.
|
||||
/datum/flock/proc/is_on_safe_z(atom/A)
|
||||
var/turf/T = get_turf(A)
|
||||
if(!T.z)
|
||||
return FALSE
|
||||
|
||||
if(flock_game_status == FLOCK_ENDGAME_LOST)
|
||||
return TRUE
|
||||
|
||||
if(is_station_level(T.z))
|
||||
return TRUE
|
||||
|
||||
return FALSE
|
||||
|
||||
/// Called when a client holding a ping image disconnects.
|
||||
/datum/flock/proc/on_client_gone(client/source)
|
||||
SIGNAL_HANDLER
|
||||
cleanup_ping_images()
|
||||
|
||||
/// Called when a unit under the flock's control dies.
|
||||
/datum/flock/proc/on_unit_death(datum/source)
|
||||
SIGNAL_HANDLER
|
||||
free_unit(source)
|
||||
|
||||
/// Called when an enemy atom is deleted.
|
||||
/datum/flock/proc/on_enemy_gone(datum/source)
|
||||
SIGNAL_HANDLER
|
||||
remove_enemy(source, TRUE)
|
||||
|
||||
/// Called when an ignored mob is deleted.
|
||||
/datum/flock/proc/on_ignore_gone(datum/source)
|
||||
SIGNAL_HANDLER
|
||||
remove_ignore(source, TRUE)
|
||||
|
||||
/// Instantiates all of the flock notice image singletons.
|
||||
/datum/flock/proc/create_hud_images()
|
||||
notice_images[FLOCK_NOTICE_RESERVED] = new /image{
|
||||
icon = 'icons/goonstation/mob/featherzone.dmi';
|
||||
icon_state = "frontier";
|
||||
plane = ABOVE_LIGHTING_PLANE;
|
||||
appearance_flags = RESET_ALPHA | RESET_COLOR | PIXEL_SCALE | RESET_TRANSFORM;
|
||||
alpha = 80;
|
||||
}
|
||||
|
||||
notice_images[FLOCK_NOTICE_PRIORITY] = new /image{
|
||||
icon = 'icons/goonstation/mob/featherzone.dmi';
|
||||
icon_state = "frontier";
|
||||
plane = ABOVE_LIGHTING_PLANE;
|
||||
appearance_flags = RESET_ALPHA | RESET_COLOR | PIXEL_SCALE | RESET_TRANSFORM;
|
||||
alpha = 180;
|
||||
}
|
||||
|
||||
notice_images[FLOCK_NOTICE_ENEMY] = new /image{
|
||||
icon = 'icons/goonstation/mob/featherzone.dmi';
|
||||
icon_state = "hazard";
|
||||
pixel_y = 16;
|
||||
plane = ABOVE_LIGHTING_PLANE;
|
||||
appearance_flags = RESET_ALPHA | RESET_COLOR | PIXEL_SCALE | RESET_TRANSFORM;
|
||||
}
|
||||
|
||||
notice_images[FLOCK_NOTICE_IGNORE] = new /image{
|
||||
icon = 'icons/goonstation/mob/featherzone.dmi';
|
||||
icon_state = "ignore";
|
||||
pixel_y = 16;
|
||||
plane = ABOVE_LIGHTING_PLANE;
|
||||
appearance_flags = RESET_ALPHA | RESET_COLOR | PIXEL_SCALE | RESET_TRANSFORM;
|
||||
}
|
||||
|
||||
notice_images[FLOCK_NOTICE_FLOCKMIND_CONTROL] = new /image{
|
||||
icon = 'icons/goonstation/mob/featherzone.dmi';
|
||||
icon_state = "flockmind_face";
|
||||
pixel_y = 16;
|
||||
plane = ABOVE_LIGHTING_PLANE;
|
||||
appearance_flags = RESET_ALPHA | RESET_COLOR | PIXEL_SCALE | RESET_TRANSFORM;
|
||||
}
|
||||
|
||||
notice_images[FLOCK_NOTICE_FLOCKTRACE_CONTROL] = new /image{
|
||||
icon = 'icons/goonstation/mob/featherzone.dmi';
|
||||
icon_state = "flocktrace_face";
|
||||
pixel_y = 16;
|
||||
plane = ABOVE_LIGHTING_PLANE;
|
||||
appearance_flags = RESET_ALPHA | RESET_COLOR | PIXEL_SCALE | RESET_TRANSFORM;
|
||||
}
|
||||
|
||||
notice_images[FLOCK_NOTICE_DECONSTRUCT] = new /image{
|
||||
icon = 'icons/goonstation/mob/featherzone.dmi';
|
||||
icon_state = "deconstruct";
|
||||
pixel_y = 16;
|
||||
plane = ABOVE_LIGHTING_PLANE;
|
||||
appearance_flags = RESET_ALPHA | RESET_COLOR | PIXEL_SCALE | RESET_TRANSFORM;
|
||||
}
|
||||
|
||||
notice_images[FLOCK_NOTICE_HEALTH] = new /image{
|
||||
icon = 'icons/goonstation/mob/featherzone.dmi';
|
||||
icon_state = "hp-100";
|
||||
pixel_x = 10;
|
||||
pixel_y = 16;
|
||||
plane = ABOVE_LIGHTING_PLANE;
|
||||
appearance_flags = RESET_ALPHA | RESET_COLOR | PIXEL_SCALE | RESET_TRANSFORM;
|
||||
}
|
||||
|
||||
/// Setter for flock_game_status.
|
||||
/datum/flock/proc/set_flock_game_status(new_status)
|
||||
var/old_status = flock_game_status
|
||||
if(old_status == new_status)
|
||||
return null
|
||||
|
||||
flock_game_status = new_status
|
||||
|
||||
return old_status
|
||||
|
||||
/// Ends the flock if it is unable to continue spreading.
|
||||
/datum/flock/proc/consider_game_over()
|
||||
if(!flock_started)
|
||||
return FALSE
|
||||
|
||||
if(flock_game_status == FLOCK_ENDGAME_LOST)
|
||||
return TRUE
|
||||
|
||||
if(flock_game_status == FLOCK_ENDGAME_VICTORY)
|
||||
return TRUE
|
||||
|
||||
if(length(drones))
|
||||
return FALSE
|
||||
|
||||
if(locate(/obj/structure/flock/egg, structures) || locate(/obj/structure/flock/rift, structures))
|
||||
return FALSE
|
||||
|
||||
game_over()
|
||||
return TRUE
|
||||
|
||||
/// Kills off the flock. Pass completely_destroy = FALSE to allow the overmind to live on.
|
||||
/datum/flock/proc/game_over(completely_destroy = TRUE)
|
||||
set waitfor = FALSE
|
||||
|
||||
// Cleanup any pings
|
||||
for(var/client/C in active_pings)
|
||||
cleanup_ping_images(C)
|
||||
|
||||
// Cleanup turf claims
|
||||
for(var/turf/T as anything in turf_reservations)
|
||||
free_turf(override_turf = T)
|
||||
|
||||
for(var/turf/flockturf as anything in claimed_floors + claimed_walls)
|
||||
stop_tracking_turf(flockturf)
|
||||
CHECK_TICK
|
||||
|
||||
// Extra lives
|
||||
if(!completely_destroy)
|
||||
refresh_unlockables()
|
||||
return
|
||||
|
||||
set_flock_game_status(FLOCK_ENDGAME_LOST)
|
||||
|
||||
// Kill overmind
|
||||
overmind?.so_very_sad_death() // Overmind can be null here if it died outside of game_over().
|
||||
|
||||
// Kill traces
|
||||
for(var/mob/camera/flock/trace/ghostbird as anything in traces)
|
||||
ghostbird.so_very_sad_death()
|
||||
|
||||
// Free units
|
||||
for(var/mob/living/basic/flock/bird as anything in (bits + drones))
|
||||
bird.dormantize()
|
||||
|
||||
// Blow up structures
|
||||
for(var/obj/structure/flock/structure as anything in structures)
|
||||
structure.deconstruct(FALSE)
|
||||
|
||||
// Remove ignores
|
||||
for(var/mob/M as anything in ignores)
|
||||
remove_ignore(M, TRUE)
|
||||
|
||||
// Remove enemies
|
||||
for(var/mob/M as anything in enemies)
|
||||
remove_enemy(M, TRUE)
|
||||
|
||||
STOP_PROCESSING(SSprocessing, src)
|
||||
|
||||
/// Called when an atom marked for deconstruction is qdeleted.
|
||||
/datum/flock/proc/deconstruct_mark_deleted(datum/source)
|
||||
SIGNAL_HANDLER
|
||||
|
||||
toggle_deconstruction_mark(source)
|
||||
@@ -0,0 +1,119 @@
|
||||
/datum/flock/ui_state(mob/user)
|
||||
return GLOB.flock_state
|
||||
|
||||
/datum/flock/ui_interact(mob/user, datum/tgui/ui)
|
||||
ui = SStgui.try_update_ui(user, src, ui)
|
||||
if(!ui)
|
||||
ui = new(user, src, "FlockPanel")
|
||||
ui.open()
|
||||
|
||||
/datum/flock/ui_data(mob/user)
|
||||
var/list/data = list()
|
||||
var/list/drone_info = list()
|
||||
var/list/trace_info = list()
|
||||
var/list/enemy_info = list()
|
||||
var/list/vitals_info = list("name" = name)
|
||||
var/list/stats_info = list()
|
||||
var/list/structure_info = list()
|
||||
|
||||
data["drones"] = drone_info
|
||||
data["partitions"] = trace_info
|
||||
data["vitals"] = vitals_info
|
||||
data["enemies"] = enemy_info
|
||||
data["structures"] = structure_info
|
||||
data["category"] = ui_tab
|
||||
data["stats"] = stats_info
|
||||
data["category_lengths"] = list(
|
||||
"traces" = length(traces),
|
||||
"drones" = length(drones),
|
||||
"enemies" = length(enemies),
|
||||
"structures" = length(structures),
|
||||
)
|
||||
|
||||
switch(ui_tab)
|
||||
if(FLOCK_UI_DRONES)
|
||||
for(var/mob/living/basic/flock/drone/bird as anything in drones)
|
||||
drone_info[++drone_info.len] = bird.get_flock_data()
|
||||
|
||||
if(FLOCK_UI_TRACES)
|
||||
for(var/mob/camera/flock/trace/ghost_bird as anything in traces)
|
||||
trace_info[++trace_info.len] = ghost_bird.get_flock_data()
|
||||
|
||||
if(FLOCK_UI_ENEMIES)
|
||||
for(var/mob/enemy in enemies)
|
||||
var/list/mob_data = list()
|
||||
mob_data["name"] = enemy.name
|
||||
mob_data["area"] = enemies[enemy]
|
||||
mob_data["ref"] = ref(enemy)
|
||||
enemy_info[++enemy_info.len] = mob_data
|
||||
|
||||
if(FLOCK_UI_STRUCTURES)
|
||||
for(var/obj/structure/flock/struct as anything in structures)
|
||||
structure_info[++structure_info.len] = struct.get_flock_data()
|
||||
|
||||
stats_info[++stats_info.len] = list(name = "Drones realized: ", "value" = stat_drones_made)
|
||||
stats_info[++stats_info.len] = list(name = "Bits formed: ", "value" = stat_bits_made)
|
||||
stats_info[++stats_info.len] = list(name = "Total deaths: ", "value" = stat_deaths)
|
||||
stats_info[++stats_info.len] = list(name = "Resources gained: ", "value" = stat_resources_gained)
|
||||
stats_info[++stats_info.len] = list(name = "Partitions divided: ", "value" = stat_traces_made)
|
||||
stats_info[++stats_info.len] = list(name = "Tiles converted: ", "value" = stat_tiles_made)
|
||||
stats_info[++stats_info.len] = list(name = "Structures materialized: ", "value" = stat_structures_made)
|
||||
stats_info[++stats_info.len] = list(name = "Highest bandwidth: ", "value" = stat_highest_bandwidth)
|
||||
|
||||
return data
|
||||
|
||||
/datum/flock/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state)
|
||||
. = ..()
|
||||
if(.)
|
||||
return
|
||||
|
||||
var/mob/user = ui.user
|
||||
switch(action)
|
||||
if("change_tab")
|
||||
if(ui_tab != params["tab"])
|
||||
ui_tab = params["tab"]
|
||||
return TRUE
|
||||
|
||||
if("jump_to")
|
||||
var/atom/movable/target = locate(params["origin"])
|
||||
var/turf/T = get_turf(target)
|
||||
if(isnull(T) || !is_on_safe_z(target))
|
||||
to_chat(user, SPAN_ALERT("They are beyond our reach."))
|
||||
return
|
||||
|
||||
if(isflockdrone(user))
|
||||
var/mob/living/basic/flock/drone/bird = user
|
||||
user = bird.controlled_by
|
||||
bird.release_control()
|
||||
|
||||
user.forceMove(T)
|
||||
|
||||
if("remove_enemy")
|
||||
var/mob/enemy = locate(params["origin"])
|
||||
if(enemy)
|
||||
remove_enemy(enemy)
|
||||
return TRUE
|
||||
|
||||
if("rally")
|
||||
var/mob/living/basic/flock/drone/bird = locate(params["origin"])
|
||||
if(istype(bird))
|
||||
bird.rally(get_turf(user))
|
||||
return TRUE
|
||||
|
||||
if("cancel_tealprint")
|
||||
var/obj/structure/flock/tealprint/tealprint = locate(params["origin"])
|
||||
tealprint?.try_cancel_structure()
|
||||
return TRUE
|
||||
|
||||
if("delete_trace")
|
||||
var/mob/camera/flock/trace/flocktrace = locate(params["origin"])
|
||||
if(!istype(flocktrace))
|
||||
message_admins("Warning: possible href exploit by [key_name(usr)] - attempted to remove a flocktrace that wasn't a flocktrace: [flocktrace]")
|
||||
log_game("Warning: possible href exploit by [key_name(usr)] - attempted to remove a flocktrace that wasn't a flocktrace: [flocktrace]")
|
||||
return TRUE
|
||||
|
||||
if(tgui_alert(user, "This will destroy the Flocktrace. Are you sure you want to do this?", "Confirmation", list("Yes", "No")) == "Yes")
|
||||
flock_talk(null, "Partition [flocktrace.real_name] has been reintegrated into flock background processes.", src, involuntary = TRUE)
|
||||
to_chat(flocktrace, SPAN_FLOCKSAY("Your higher cognition has been forcibly reintegrated into the collective will of the flock."))
|
||||
flocktrace.so_very_sad_death()
|
||||
return TRUE
|
||||
@@ -0,0 +1,141 @@
|
||||
/proc/flock_convert_turf(turf/T, datum/flock/flock, force)
|
||||
if(!T?.can_flock_convert(force))
|
||||
return
|
||||
|
||||
if(isflockturf(T))
|
||||
. = T
|
||||
|
||||
else if(iswallturf(T))
|
||||
. = T.ChangeTurf(/turf/simulated/wall/flock, ignore_air = TRUE)
|
||||
|
||||
else if(isfloorturf(T))
|
||||
. = T.ChangeTurf(/turf/simulated/floor/flock, ignore_air = TRUE)
|
||||
var/list/datum/element/decal/decals = T.get_decals()
|
||||
for(var/datum/element/decal/dcl in decals)
|
||||
dcl.Detach(T)
|
||||
T.RemoveElement(/datum/element/decal)
|
||||
|
||||
var/obj/structure/lattice/L = locate() in .
|
||||
if(L)
|
||||
qdel(L)
|
||||
if(istype(L, /obj/structure/lattice/catwalk))
|
||||
. = T.ChangeTurf(/turf/simulated/floor/flock)
|
||||
|
||||
for(var/obj/O in .)
|
||||
if(iseffect(O))
|
||||
continue
|
||||
|
||||
O.try_flock_convert(flock, force)
|
||||
|
||||
/// Attempt to convert an object. Default behavior is to do nothing.
|
||||
/obj/proc/try_flock_convert(datum/flock/flock, force)
|
||||
return
|
||||
|
||||
/obj/machinery/camera/try_flock_convert(datum/flock/flock, force)
|
||||
deconstruct()
|
||||
|
||||
/obj/structure/window/try_flock_convert(datum/flock/flock, force)
|
||||
var/turf/T = loc
|
||||
var/obj/structure/window/flock/new_window
|
||||
qdel(src)
|
||||
|
||||
if(fulltile)
|
||||
new_window = new /obj/structure/window/flock/fulltile(T)
|
||||
else
|
||||
new_window = new /obj/structure/window/flock(T)
|
||||
new_window.dir = dir
|
||||
|
||||
new_window.AddComponent(/datum/component/flock_interest, flock)
|
||||
return new_window
|
||||
|
||||
/obj/machinery/door/try_flock_convert(datum/flock/flock, force)
|
||||
var/turf/T = loc
|
||||
qdel(src)
|
||||
return new /obj/machinery/door/flock(T)
|
||||
|
||||
// This results in double layered doors
|
||||
/obj/machinery/door/firedoor/try_flock_convert(datum/flock/flock, force)
|
||||
return
|
||||
|
||||
/obj/machinery/light/try_flock_convert(datum/flock/flock, force)
|
||||
var/obj/L = new /obj/machinery/light/flock(loc)
|
||||
L.setDir(dir)
|
||||
qdel(src)
|
||||
return L
|
||||
|
||||
/obj/machinery/light/floor/try_flock_convert(datum/flock/flock, force)
|
||||
. = new /obj/machinery/light/floor/has_bulb/flock(loc)
|
||||
qdel(src)
|
||||
|
||||
/obj/structure/computerframe/try_flock_convert(datum/flock/flock, force)
|
||||
. = new /obj/structure/flock/compute(loc, flock)
|
||||
qdel(src)
|
||||
|
||||
/obj/machinery/computer4/try_flock_convert(datum/flock/flock, force)
|
||||
. = new /obj/structure/flock/compute(loc, flock)
|
||||
qdel(src)
|
||||
|
||||
/obj/machinery/computer/try_flock_convert(datum/flock/flock, force)
|
||||
. = new /obj/structure/flock/compute(loc, flock)
|
||||
qdel(src)
|
||||
|
||||
/obj/machinery/seed_extractor/try_flock_convert(datum/flock/flock, force)
|
||||
. = new /obj/structure/flock/compute(loc, flock)
|
||||
qdel(src)
|
||||
|
||||
/obj/machinery/telecomms/try_flock_convert(datum/flock/flock, force)
|
||||
. = new /obj/structure/flock/compute(loc, flock)
|
||||
qdel(src)
|
||||
|
||||
/obj/structure/chair/try_flock_convert(datum/flock/flock, force)
|
||||
. = ..()
|
||||
var/obj/structure/chair/comfy/flock/F = new /obj/structure/chair/comfy/flock(get_turf(src), flock)
|
||||
F.AddComponent(/datum/component/flock_interest, flock)
|
||||
F.setDir(dir)
|
||||
qdel(src)
|
||||
|
||||
/obj/structure/lattice/try_flock_convert(datum/flock/flock, force)
|
||||
. = ..()
|
||||
var/obj/structure/lattice/flock/F = new /obj/structure/lattice/flock(get_turf(src))
|
||||
F.AddComponent(/datum/component/flock_interest, flock)
|
||||
qdel(src)
|
||||
|
||||
/obj/structure/closet/try_flock_convert(datum/flock/flock, force)
|
||||
. = ..()
|
||||
dump_contents()
|
||||
var/obj/structure/closet/flock/F = new(get_turf(src), flock)
|
||||
F.take_contents()
|
||||
F.AddComponent(/datum/component/flock_interest, flock)
|
||||
qdel(src)
|
||||
|
||||
/obj/machinery/porta_turret/try_flock_convert(datum/flock/flock, force)
|
||||
. = ..()
|
||||
new /obj/structure/flock/gnesis_turret(get_turf(src), flock)
|
||||
qdel(src)
|
||||
|
||||
/obj/structure/grille/try_flock_convert(datum/flock/flock, force)
|
||||
var/obj/structure/grille/G
|
||||
if(broken)
|
||||
G = new /obj/structure/grille/flock/broken(get_turf(src))
|
||||
else
|
||||
G = new /obj/structure/grille/flock(get_turf(src))
|
||||
G.AddComponent(/datum/component/flock_interest, flock)
|
||||
qdel(src)
|
||||
|
||||
/turf/proc/can_flock_convert(force)
|
||||
return FALSE
|
||||
|
||||
/turf/space/can_flock_convert(force)
|
||||
var/obj/structure/lattice/L = locate() in src
|
||||
if(L)
|
||||
return TRUE
|
||||
return FALSE
|
||||
|
||||
/turf/simulated/floor/can_flock_convert(force)
|
||||
return TRUE
|
||||
|
||||
/turf/simulated/floor/flock/can_flock_convert(force)
|
||||
return TRUE
|
||||
|
||||
/turf/simulated/wall/can_flock_convert(force)
|
||||
return TRUE
|
||||
@@ -0,0 +1,23 @@
|
||||
/obj/effect/temp_visual/flock // temporary flock visual feedback objects
|
||||
icon = 'icons/goonstation/mob/featherzone.dmi'
|
||||
icon_state = "blank"
|
||||
layer = ABOVE_NORMAL_TURF_LAYER
|
||||
duration = 4.5 SECONDS
|
||||
var/animation
|
||||
|
||||
/obj/effect/temp_visual/flock/Initialize(mapload)
|
||||
. = ..()
|
||||
flick(animation, src)
|
||||
|
||||
/obj/effect/temp_visual/flock/wall_convert
|
||||
icon_state = "spawn-wall-loop"
|
||||
animation = "spawn-wall"
|
||||
|
||||
/obj/effect/temp_visual/flock/floor_convert
|
||||
icon_state = "spawn-floor-loop"
|
||||
animation = "spawn-floor"
|
||||
|
||||
/obj/effect/temp_visual/flock/flock_heal_burst
|
||||
duration = 0.7 SECONDS
|
||||
icon_state = "telemarker"
|
||||
animation = "telemarker"
|
||||
@@ -0,0 +1,54 @@
|
||||
GLOBAL_LIST_INIT(flockdrone_created_phrases, list(
|
||||
"online, flock protocols initiating",
|
||||
"awoken, concordance achieved",
|
||||
"existence achieved, analysing situation",
|
||||
"assembling process complete",
|
||||
))
|
||||
|
||||
GLOBAL_LIST_INIT(flockdrone_death_phrases, list(
|
||||
"terminal damage, system malf---",
|
||||
"situation untenable, death imminen---",
|
||||
"severe damage, unable to recov---",
|
||||
"notification: unit death---"
|
||||
))
|
||||
|
||||
GLOBAL_LIST_INIT(flockdrone_new_enemy_phrases, list(
|
||||
"new enemy,",
|
||||
"new transgressor detected,",
|
||||
"new threat is,",
|
||||
"damage sustained, no thanks to",
|
||||
"new violent individual, be warned, ident is",
|
||||
"please destroy"
|
||||
))
|
||||
|
||||
GLOBAL_LIST_INIT(flockdrone_name_adjectives, list(
|
||||
"weird",
|
||||
"odd",
|
||||
"strange",
|
||||
"unusual",
|
||||
"distinctive",
|
||||
"glowy",
|
||||
"flashy",
|
||||
"shiny",
|
||||
"bright",
|
||||
"pulsing",
|
||||
"gaudy",
|
||||
"flickering",
|
||||
"humming"
|
||||
))
|
||||
|
||||
GLOBAL_LIST_INIT(flockdrone_name_nouns, list(
|
||||
"thing",
|
||||
"device",
|
||||
"machine",
|
||||
"construct",
|
||||
"apparatus",
|
||||
"object"
|
||||
))
|
||||
|
||||
GLOBAL_LIST_INIT(flock_betrayal_phrases, list(
|
||||
"disregarding attack from ignored target",
|
||||
"turning a blind eye to",
|
||||
"revenge not taken on",
|
||||
"ignoring attack from",
|
||||
))
|
||||
@@ -0,0 +1,3 @@
|
||||
/// Returns the name that the flock has given an object. Flavor.
|
||||
/atom/proc/get_flock_id()
|
||||
return
|
||||
@@ -0,0 +1,25 @@
|
||||
/obj/item/reagent_containers/glass/gnesis
|
||||
name = "fluid-filled octahedron"
|
||||
desc = "A strange container made of a crystalline matrix. It looks like it can hold reagents but it's a mystery how to access them."
|
||||
icon = 'icons/goonstation/mob/featherzone.dmi'
|
||||
icon_state = "minicache"
|
||||
inhand_icon_state = "beaker"
|
||||
possible_transfer_amounts = list(5, 10, 15, 25, 30, 50, 100, 200)
|
||||
has_lid = FALSE
|
||||
volume = 200
|
||||
container_type = REFILLABLE | DRAINABLE
|
||||
materials = list(MAT_GNESIS_GLASS = 2000)
|
||||
|
||||
/obj/item/reagent_containers/glass/gnesis/examine(mob/user)
|
||||
if(!isflockmob(user))
|
||||
return ..()
|
||||
|
||||
. = list(
|
||||
SPAN_FLOCKSAY("<b>###=- Ident confirmed, data packet received.</b>"),
|
||||
SPAN_FLOCKSAY("<b>ID:</b> Reagent Minicache"),
|
||||
SPAN_FLOCKSAY("<b>System Storage:</b> [(reagents.total_volume / volume)]% filled."),
|
||||
SPAN_FLOCKSAY("<b>###=-</b>")
|
||||
)
|
||||
|
||||
/obj/item/reagent_containers/glass/gnesis/prefilled
|
||||
list_reagents = list("gnesis_tox" = 50)
|
||||
@@ -0,0 +1,26 @@
|
||||
// The cuuuuuuuuuuuuuube
|
||||
/obj/item/flock_cube
|
||||
name = "weird cube"
|
||||
desc = "A weird looking cube."
|
||||
icon_state = "cube"
|
||||
icon = 'icons/goonstation/mob/featherzone.dmi'
|
||||
light_color = "#26ffe6"
|
||||
light_power = 0.2
|
||||
light_range = 2
|
||||
|
||||
materials = list(MAT_METAL = 1000, MAT_GLASS = 1000, MAT_GNESIS = 2000, MAT_GNESIS_GLASS = 2000)
|
||||
origin_tech = "materials=5;biotech=5;programming=5"
|
||||
|
||||
/// How much shit in da cube
|
||||
var/substrate = 10
|
||||
|
||||
/obj/item/flock_cube/examine(mob/user)
|
||||
if(!isflockmob(user))
|
||||
return ..()
|
||||
|
||||
. = list(
|
||||
SPAN_FLOCKSAY("<b>###=- Ident confirmed, data packet received.</b>"),
|
||||
SPAN_FLOCKSAY("<b>ID:</b> Substrate Cube"),
|
||||
SPAN_FLOCKSAY("<b>System Storage:</b> [substrate] units"),
|
||||
SPAN_FLOCKSAY("<b>###=-</b>")
|
||||
)
|
||||
@@ -0,0 +1,79 @@
|
||||
/datum/language/flock
|
||||
name = "Symphonic"
|
||||
speech_verb = "caws"
|
||||
space_chance = 60
|
||||
key = "f"
|
||||
flags = RESTRICTED | HIVEMIND | NOLIBRARIAN | NOBABEL | NO_STUTTER
|
||||
colour = "flocksay"
|
||||
syllables = list("c#aw", "kr%k", "c@rk", "ka~w", "cr!k", "sk^a", "r#aw", "c*rr", "ka%x", "t~ch", "gr!k", "c&aw", "k!rr", "ch#k", "ra%k", "sk@w", "tr^k", "c~ra", "kw!p", "z#aw", "cl%k", "r@rk", "sh^k", "k~ra", "v!aw")
|
||||
|
||||
/datum/language/flock/get_spoken_verb(msg_end)
|
||||
switch(msg_end)
|
||||
if("!")
|
||||
return pick(exclaim_verbs)
|
||||
if("?")
|
||||
return ask_verb
|
||||
return pick("sings", "clicks", "whistles", "intones", "transmits", "submits", "uploads", "caws")
|
||||
|
||||
/datum/language/flock/format_message(message)
|
||||
return SPAN_FLOCKSAY_GRADIENT("[get_spoken_verb(message)] \"[message]\"")
|
||||
|
||||
/datum/language/flock/broadcast(atom/speaker, message, speaker_mask, involuntary = FALSE)
|
||||
var/log_message = "(FLOCK) [message]"
|
||||
if(ismob(speaker))
|
||||
var/mob/M = speaker
|
||||
log_say(log_message, speaker)
|
||||
M.create_log(SAY_LOG, log_message)
|
||||
|
||||
var/list/message_start
|
||||
var/list/message_body = list(format_message(message))
|
||||
|
||||
if(!speaker)
|
||||
message_start = list("<i><span class='game say'>[name]</i>: ", SPAN_FLOCKSAY_GRADIENT("System"))
|
||||
else if(!ismob(speaker))
|
||||
message_start = list("<i><span class='game say'>[name]</i>: ", SPAN_FLOCKSAY_GRADIENT("[speaker.name]"))
|
||||
else if(isflockcontroller(speaker))
|
||||
var/mob/speaking_mob = speaker
|
||||
message_start = list("<i><font size=4><span class='game say'>[name]</i>: ", SPAN_FLOCKSAY_GRADIENT("[isflockmind(speaker) ? "" : "Flocktrace "][speaking_mob.real_name]"))
|
||||
message_body += "</font>"
|
||||
else if(isflockworker(speaker))
|
||||
var/mob/speaking_mob = speaker
|
||||
message_start = list("<i><span class='game say'>[name]</i>: ", SPAN_FLOCKSAY_GRADIENT("Drone [speaking_mob.real_name]"))
|
||||
|
||||
for(var/mob/M in GLOB.dead_mob_list)
|
||||
if(!isnewplayer(M) && !isbrain(M) && speaker)
|
||||
var/list/message_start_dead = list("<i><span class='game say'>[name], [SPAN_NAME("[speaker.name] ([ghost_follow_link(speaker, ghost=M)])")]")
|
||||
var/list/dead_message = message_start_dead + message_body
|
||||
M.show_message(dead_message.Join(" "), 2)
|
||||
|
||||
if(speaker && !isflockcontroller(speaker))
|
||||
var/scrambled = SPAN_FLOCKSAY_GRADIENT(scramble(message))
|
||||
var/living_msg = "[speaker.name] [get_spoken_verb(message)] \"[scrambled]\""
|
||||
for(var/mob/M in hearers(5, get_turf(speaker)))
|
||||
M.show_message(living_msg, 2)
|
||||
if(M.client?.prefs.toggles2 & PREFTOGGLE_2_RUNECHAT && ismob(speaker))
|
||||
var/mob/speaking_mob = speaker
|
||||
M.create_chat_message(locateUID(speaking_mob.runechat_msg_location), scrambled)
|
||||
|
||||
for(var/mob/F in GLOB.alive_mob_list)
|
||||
if(!isflockmob(F))
|
||||
continue
|
||||
var/list/final_message = message_start + message_body
|
||||
F.show_message(final_message.Join(" "), 2)
|
||||
if(F.client?.prefs.toggles2 & PREFTOGGLE_2_RUNECHAT && ismob(speaker))
|
||||
var/mob/speaking_mob = speaker
|
||||
F.create_chat_message(locateUID(speaking_mob.runechat_msg_location), message)
|
||||
|
||||
var/list/silicon_message_start = list("<i><span class='game say'>Robot Talk, [SPAN_NAME("Strange Static")]")
|
||||
var/silicon_message_content = Gibberish(message, 70, replace_rate = 50)
|
||||
var/silicon_message = SPAN_ROBOT(silicon_message_content)
|
||||
var/list/final_message_silicon = silicon_message_start + silicon_message
|
||||
if(involuntary || !prob(30))
|
||||
return
|
||||
for(var/mob/living/silicon/S in GLOB.alive_mob_list)
|
||||
if(!S.binarycheck())
|
||||
continue
|
||||
S.show_message(final_message_silicon.Join(" "), 2)
|
||||
if(isflockworker(speaker) && S.client?.prefs.toggles2 & PREFTOGGLE_2_RUNECHAT && ismob(speaker))
|
||||
var/mob/speaking_mob = speaker
|
||||
S.create_chat_message(locateUID(speaking_mob.runechat_msg_location), silicon_message_content)
|
||||
@@ -0,0 +1,224 @@
|
||||
/mob/living/basic/flock
|
||||
name = "flockdrone"
|
||||
icon = 'icons/goonstation/mob/featherzone.dmi'
|
||||
icon_state = "drone"
|
||||
icon_living = "drone"
|
||||
icon_dead = "drone-dead"
|
||||
|
||||
pass_flags_self = PASSTABLE | PASSFLOCK
|
||||
|
||||
a_intent = INTENT_DISARM
|
||||
|
||||
light_color = "#26ffe6"
|
||||
light_power = 0.2
|
||||
light_range = 2
|
||||
|
||||
faction = list("flockmind")
|
||||
ai_controller = /datum/ai_controller/flock
|
||||
|
||||
sentience_type = SENTIENCE_OTHER
|
||||
|
||||
minimum_survivable_temperature = 0
|
||||
maximum_survivable_temperature = 1000
|
||||
atmos_requirements = list("min_oxy" = 0, "max_oxy" = 0, "min_tox" = 0, "max_tox" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0)
|
||||
damage_coeff = list(BRUTE = 1.5, BURN = 0.2, TOX = 0.2, CLONE = 0, STAMINA = 0, OXY = 0)
|
||||
|
||||
initial_traits = list(TRAIT_FLYING, TRAIT_FLOCK_THING)
|
||||
|
||||
butcher_results = list(/obj/item/stack/sheet/gnesis = 1)
|
||||
blood_color = "#4d736d"
|
||||
blood_volume = BLOOD_VOLUME_OKAY
|
||||
|
||||
contains_xeno_organ = TRUE
|
||||
|
||||
var/list/actions_to_grant = list(
|
||||
/datum/action/cooldown/flock/convert,
|
||||
)
|
||||
|
||||
var/icon_dormant = "drone-dormant"
|
||||
|
||||
/// Flock datum. Can be null.
|
||||
var/tmp/datum/flock/flock
|
||||
|
||||
/// Physical resources for constructing structures or flockrunning.
|
||||
var/tmp/datum/point_holder/substrate
|
||||
|
||||
/// Type of point holder to use
|
||||
var/point_holder_type = /datum/point_holder
|
||||
|
||||
/// Flock ID nametag
|
||||
var/tmp/obj/effect/abstract/info_tag/flock/name_tag
|
||||
|
||||
/// Tag for the mob's current AI task.
|
||||
var/tmp/obj/effect/abstract/info_tag/flock/info/task_tag
|
||||
|
||||
var/bandwidth_provided = 0
|
||||
|
||||
/// If, while part of an active flock, a flockmob leaves the station, they become dormant.
|
||||
var/tmp/dormant = FALSE
|
||||
|
||||
/mob/living/basic/flock/Initialize(mapload, join_flock)
|
||||
. = ..()
|
||||
RegisterSignal(ai_controller, COMSIG_AI_STATUS_CHANGE, PROC_REF(on_ai_status_change))
|
||||
|
||||
for(var/action_path in actions_to_grant)
|
||||
var/datum/action/action = new action_path
|
||||
action.Grant(src)
|
||||
|
||||
flock = join_flock || get_default_flock()
|
||||
flock?.add_unit(src)
|
||||
flock.add_notice(src, FLOCK_NOTICE_HEALTH)
|
||||
|
||||
substrate = new point_holder_type
|
||||
|
||||
name_tag = new()
|
||||
name_tag.set_parent(src)
|
||||
|
||||
task_tag = new()
|
||||
task_tag.set_parent(src)
|
||||
|
||||
add_language("Symphonic")
|
||||
set_default_language(GLOB.all_languages["Symphonic"])
|
||||
|
||||
/mob/living/basic/flock/Destroy()
|
||||
flock?.free_unit(src)
|
||||
flock = null
|
||||
QDEL_NULL(name_tag)
|
||||
QDEL_NULL(task_tag)
|
||||
return ..()
|
||||
|
||||
/mob/living/basic/flock/gib()
|
||||
if(!death(TRUE) && stat != DEAD)
|
||||
return FALSE
|
||||
playsound(src.loc, 'sound/goonstation/effects/robogib.ogg', 50, 1)
|
||||
|
||||
flockgibs(loc)
|
||||
|
||||
GLOB.alive_mob_list -= src
|
||||
GLOB.dead_mob_list -= src
|
||||
qdel(src)
|
||||
return TRUE
|
||||
|
||||
/mob/living/basic/flock/Process_Spacemove(movement_dir = 0, continuous_move = FALSE)
|
||||
return TRUE
|
||||
|
||||
/mob/living/basic/flock/set_stat(new_stat)
|
||||
. = ..()
|
||||
if(stat != DEAD)
|
||||
ADD_TRAIT(src, TRAIT_FLYING, INNATE_TRAIT)
|
||||
else
|
||||
REMOVE_TRAIT(src, TRAIT_FLYING, INNATE_TRAIT)
|
||||
|
||||
/mob/living/basic/flock/update_name(updates)
|
||||
. = ..()
|
||||
name_tag?.set_text(real_name)
|
||||
|
||||
/mob/living/basic/flock/update_icon_state()
|
||||
if(stat == DEAD)
|
||||
icon_state = icon_dead
|
||||
else
|
||||
if(dormant)
|
||||
icon_state = icon_dormant
|
||||
else
|
||||
icon_state = icon_living
|
||||
return ..()
|
||||
|
||||
/mob/living/basic/flock/say(message, verb, sanitize, ignore_speech_problems, ignore_atmospherics, ignore_languages, automatic, bigvoice, forced = FALSE)
|
||||
. = ..()
|
||||
if(!.)
|
||||
return
|
||||
|
||||
/mob/living/basic/flock/updatehealth(cause_of_death)
|
||||
. = ..()
|
||||
update_health_notice()
|
||||
|
||||
/mob/living/basic/flock/proc/update_health_notice()
|
||||
if(!flock)
|
||||
return
|
||||
|
||||
var/datum/alternate_appearance/notice = get_alt_appearance(FLOCK_NOTICE_HEALTH)
|
||||
if(!notice)
|
||||
notice = flock.add_notice(src, FLOCK_NOTICE_HEALTH)
|
||||
|
||||
var/image/I = notice.img
|
||||
I.icon_state = "hp-[round(get_damage_percent(), 10)]"
|
||||
|
||||
/mob/living/basic/flock/on_changed_z_level(turf/old_turf, turf/new_turf)
|
||||
. = ..()
|
||||
if(flock && new_turf && !flock.is_on_safe_z(new_turf))
|
||||
dormantize()
|
||||
|
||||
/mob/living/basic/flock/death(gibbed, cause_of_death)
|
||||
flock?.remove_notice(src, FLOCK_NOTICE_HEALTH)
|
||||
flock?.free_unit(src)
|
||||
flock?.stat_deaths++
|
||||
return ..()
|
||||
|
||||
/mob/living/basic/flock/get_flock_id()
|
||||
return real_name
|
||||
|
||||
/mob/living/basic/flock/proc/get_flock_data()
|
||||
var/list/data = list()
|
||||
data["name"] = real_name
|
||||
data["health"] = get_damage_percent()
|
||||
data["resources"] = substrate.has_points()
|
||||
data["area"] = get_area_name(src, TRUE) || "???"
|
||||
data["ref"] = ref(src)
|
||||
return data
|
||||
|
||||
/// Become dormant. A husk. A shell.
|
||||
/mob/living/basic/flock/proc/dormantize()
|
||||
if(dormant)
|
||||
return
|
||||
|
||||
dormant = TRUE
|
||||
|
||||
ai_controller.set_ai_status(AI_STATUS_OFF)
|
||||
flock?.free_unit(src)
|
||||
update_light_state()
|
||||
|
||||
update_appearance(UPDATE_ICON_STATE)
|
||||
|
||||
/mob/living/basic/flock/proc/rally(turf/location)
|
||||
if(!isturf(location))
|
||||
return
|
||||
|
||||
if(ai_controller.ai_status == AI_STATUS_OFF || ckey)
|
||||
return
|
||||
|
||||
ai_controller.cancel_actions()
|
||||
ai_controller.queue_behavior(/datum/ai_behavior/flock/rally, location)
|
||||
|
||||
/// Helper for keeping consistency across tesk name sets.
|
||||
/mob/living/basic/flock/proc/set_task_desc(text)
|
||||
task_tag?.set_text("Task: [text || "idling"]")
|
||||
|
||||
/// Turn the light on or off, based on if the mob is doing shit or not.
|
||||
/mob/living/basic/flock/proc/update_light_state()
|
||||
if(stat == DEAD || dormant)
|
||||
set_light(0, 0, light_color)
|
||||
return
|
||||
|
||||
if(ai_controller.ai_status == AI_ON || ckey)
|
||||
set_light(2, 0.2, light_color)
|
||||
return
|
||||
|
||||
set_light(0, 0, light_color)
|
||||
|
||||
/mob/living/basic/flock/proc/on_ai_status_change(datum/ai_controller/source, ai_status)
|
||||
SIGNAL_HANDLER
|
||||
update_light_state()
|
||||
|
||||
/mob/living/basic/flock/vv_edit_var(var_name, var_value)
|
||||
switch(var_name)
|
||||
if(NAMEOF(src, bandwidth_provided))
|
||||
flock?.bandwidth.adjust_points(-bandwidth_provided)
|
||||
..()
|
||||
flock?.bandwidth.adjust_points(bandwidth_provided)
|
||||
return TRUE
|
||||
|
||||
/mob/living/basic/flock/CanPass(atom/movable/mover, border_dir)
|
||||
if(istype(mover, /obj/projectile/energy/flock_bolt))
|
||||
return TRUE
|
||||
|
||||
return ..()
|
||||
@@ -0,0 +1,47 @@
|
||||
/mob/living/basic/flock/bit
|
||||
name = "flockbit"
|
||||
icon_state = "flockbit"
|
||||
icon_dormant = "bit-dormant"
|
||||
desc = "The simplest partition of the flockmind, working autonomously without heed for itself as it dutifully serves the flockmind."
|
||||
|
||||
move_force = MOVE_FORCE_VERY_WEAK
|
||||
maxHealth = 10
|
||||
health = 10
|
||||
density = FALSE
|
||||
pass_flags = PASSTABLE
|
||||
|
||||
surgery_container = /datum/xenobiology_surgery_container/flockbit
|
||||
// Flockbits don't get specific AI behaviors that would make this broken.
|
||||
point_holder_type = /datum/point_holder/infinite
|
||||
|
||||
/mob/living/basic/flock/bit/Initialize(mapload)
|
||||
. = ..()
|
||||
flock?.stat_bits_made++
|
||||
AddComponent(/datum/component/flock_protection)
|
||||
real_name = flock_realname(FLOCK_TYPE_BIT)
|
||||
name = flock_name(FLOCK_TYPE_BIT)
|
||||
|
||||
/mob/living/basic/flock/bit/examine(mob/user)
|
||||
if(!isflockmob(user))
|
||||
return ..()
|
||||
|
||||
. = list(
|
||||
SPAN_FLOCKSAY("<b>###=- Ident confirmed, data packet received.</b>"),
|
||||
SPAN_FLOCKSAY("<b>ID:</b> [real_name]"),
|
||||
SPAN_FLOCKSAY("<b>Flock:</b> [flock?.name || "N/A"]"),
|
||||
SPAN_FLOCKSAY("<b>System Integrity: [round(health / maxHealth, 0.1) * 100]</b>"),
|
||||
SPAN_FLOCKSAY("<b>Cognition:</b> [HAS_TRAIT(src, TRAIT_AI_PAUSED) ? "AWARE" : "PREDEFINED"]"),
|
||||
SPAN_FLOCKSAY("<b>###=-</b>"),
|
||||
)
|
||||
|
||||
/mob/living/basic/flock/bit/proc/i_just_split(turf/avoid)
|
||||
ai_controller.pause_ai(0.3 SECONDS)
|
||||
addtimer(CALLBACK(src, PROC_REF(step_away_from), avoid), 0.2 SECONDS)
|
||||
|
||||
/mob/living/basic/flock/bit/proc/step_away_from(turf/avoid)
|
||||
if(avoid == get_turf(src))
|
||||
step(src, pick(GLOB.alldirs))
|
||||
return
|
||||
|
||||
var/list/turfs = RANGE_TURFS(1, src) - get_turf(src)
|
||||
step(src, get_dir(src, pick(turfs)))
|
||||
@@ -0,0 +1,470 @@
|
||||
/mob/living/basic/flock/drone
|
||||
desc = "The physical partitions of the flockmind, forever enacting its will until its job is complete."
|
||||
hud_type = /datum/hud/flockdrone
|
||||
ai_controller = /datum/ai_controller/flock/drone
|
||||
|
||||
move_force = MOVE_FORCE_WEAK
|
||||
|
||||
actions_to_grant = list(
|
||||
/datum/action/cooldown/flock/release_control,
|
||||
/datum/action/cooldown/flock/nest,
|
||||
/datum/action/cooldown/flock/convert,
|
||||
/datum/action/cooldown/flock/deconstruct,
|
||||
/datum/action/cooldown/flock/flock_heal,
|
||||
/datum/action/cooldown/flock/cage_mob,
|
||||
/datum/action/cooldown/flock/deposit,
|
||||
)
|
||||
surgery_container = /datum/xenobiology_surgery_container/flockdrone
|
||||
|
||||
bandwidth_provided = FLOCK_COMPUTE_COST_DRONE
|
||||
|
||||
initial_traits = list(TRAIT_FLYING, TRAIT_FLOCK_THING, TRAIT_IMPORTANT_SPEAKER)
|
||||
|
||||
/// A mob possessing this mob.
|
||||
var/tmp/mob/camera/flock/controlled_by
|
||||
|
||||
var/list/parts = list()
|
||||
/// Active flockdrone part.
|
||||
var/datum/flockdrone_part/active_part
|
||||
|
||||
/mob/living/basic/flock/drone/Initialize(mapload, join_flock)
|
||||
. = ..()
|
||||
create_parts()
|
||||
var/datum/flockdrone_part/part = parts[1]
|
||||
set_active_part(part)
|
||||
flock?.stat_drones_made++
|
||||
|
||||
AddComponent(/datum/component/flock_protection, FALSE, TRUE, FALSE, FALSE)
|
||||
real_name = flock_realname(FLOCK_TYPE_DRONE)
|
||||
name = flock_name(FLOCK_TYPE_DRONE)
|
||||
|
||||
if(stat == CONSCIOUS)
|
||||
flock_talk(src, pick(GLOB.flockdrone_created_phrases), flock, TRUE)
|
||||
|
||||
/mob/living/basic/flock/drone/Destroy()
|
||||
release_control()
|
||||
QDEL_NULL(substrate)
|
||||
QDEL_LIST_CONTENTS(parts)
|
||||
active_part = null // whatever was here was qdeleted by qdel_list(parts)
|
||||
return ..()
|
||||
|
||||
/mob/living/basic/flock/drone/examine(mob/user)
|
||||
if(!isflockmob(user))
|
||||
return ..()
|
||||
|
||||
var/cognition = "TORPID"
|
||||
if(stat == DEAD)
|
||||
cognition = "DEAD"
|
||||
else if(controlled_by || ckey)
|
||||
cognition = "SAPIENT"
|
||||
else if(dormant)
|
||||
cognition = "ABSENT"
|
||||
else if(HAS_TRAIT(src, TRAIT_AI_PAUSED))
|
||||
cognition = "HIBERNATING"
|
||||
|
||||
. = list(
|
||||
SPAN_FLOCKSAY("<b>###=- Ident confirmed, data packet received.</b>"),
|
||||
controlled_by ? SPAN_FLOCKSAY("<b>ID:</b> [controlled_by.real_name] (controlling [real_name])") : SPAN_FLOCKSAY("<b>ID:</b> [real_name]"),
|
||||
SPAN_FLOCKSAY("<b>Flock:</b> [flock?.name || "N/A"]"),
|
||||
SPAN_FLOCKSAY("<b>Substrate: [substrate.has_points()]</b>"),
|
||||
SPAN_FLOCKSAY("<b>System Integrity: [round(health / maxHealth, 0.1) * 100]</b>"),
|
||||
SPAN_FLOCKSAY("<b>Cognition:</b> [cognition]"),
|
||||
)
|
||||
|
||||
if(cognition == "TORPID" && length(ai_controller?.current_behaviors))
|
||||
var/datum/ai_behavior/flock/flock_behavior = locate() in ai_controller.current_behaviors
|
||||
if(istype(flock_behavior))
|
||||
. += SPAN_FLOCKSAY("<b>Task: [flock_behavior.name]")
|
||||
|
||||
. += SPAN_FLOCKSAY("<b>###=-</b>")
|
||||
|
||||
/mob/living/basic/flock/drone/death(gibbed)
|
||||
stop_flockphase(TRUE)
|
||||
release_control()
|
||||
say(pick(GLOB.flockdrone_death_phrases))
|
||||
if(flock)
|
||||
flock_talk(null, "Connection to drone [real_name] lost.", flock, involuntary = TRUE)
|
||||
|
||||
var/datum/flockdrone_part/absorber/absorber = locate() in parts
|
||||
absorber.try_drop_item()
|
||||
return ..()
|
||||
|
||||
/mob/living/basic/flock/drone/Life(seconds_per_tick, times_fired)
|
||||
. = ..()
|
||||
if(HAS_TRAIT(src, TRAIT_FLOCKPHASE))
|
||||
if(avoid_stop_flockphase())
|
||||
flockphase_tax()
|
||||
else
|
||||
stop_flockphase()
|
||||
|
||||
/mob/living/basic/flock/drone/Moved(atom/old_loc, movement_dir, forced, list/old_locs, momentum_change)
|
||||
. = ..()
|
||||
|
||||
// Stop existing flockphasing if we can't flockphase, also update the oldloc if it was a flock floor.
|
||||
if(HAS_TRAIT(src, TRAIT_FLOCKPHASE))
|
||||
if(istype(old_loc, /turf/simulated/floor/flock))
|
||||
var/turf/simulated/floor/flock/flockfloor = old_loc
|
||||
LAZYREMOVE(flockfloor.flockrunning_mobs, src)
|
||||
flockfloor.update_power()
|
||||
|
||||
// Mob can't flockphase.
|
||||
if(!isflockturf(loc) || !can_flockphase())
|
||||
stop_flockphase(TRUE)
|
||||
return
|
||||
|
||||
// Being in a wall forces flockphase if able.
|
||||
if(istype(loc, /turf/simulated/wall/flock))
|
||||
if(flockphase_tax())
|
||||
start_flockphase()
|
||||
return
|
||||
return
|
||||
|
||||
// Either the client wants to flockphase, or if uncliented, already flockphasing.
|
||||
var/wants_to_flockphase = HAS_TRAIT(src, TRAIT_FLOCKPHASE)
|
||||
if(!wants_to_flockphase)
|
||||
stop_flockphase()
|
||||
return
|
||||
|
||||
if(istype(loc, /turf/simulated/floor/flock))
|
||||
var/turf/simulated/floor/flock/flockfloor = loc
|
||||
// Also incapable of flockphasing.
|
||||
if(flockfloor.broken)
|
||||
stop_flockphase(TRUE)
|
||||
return
|
||||
|
||||
if(flockphase_tax())
|
||||
start_flockphase()
|
||||
LAZYADD(flockfloor.flockrunning_mobs, src)
|
||||
flockfloor.update_power()
|
||||
return
|
||||
return
|
||||
|
||||
/mob/living/basic/flock/drone/get_status_tab_items()
|
||||
var/list/status_tab_data = ..()
|
||||
. = status_tab_data
|
||||
status_tab_data[++status_tab_data.len] = list("Substrate: ", "[substrate.has_points()]")
|
||||
status_tab_data[++status_tab_data.len] = list("", "")
|
||||
switch(flock.flock_game_status)
|
||||
if(NONE)
|
||||
var/turf_status = min(100, floor((length(flock.claimed_floors) + length(flock.claimed_walls)) / FLOCK_TURFS_FOR_RELAY * 100))
|
||||
var/bandwith_status = min(100, floor(flock.available_bandwidth() / FLOCK_COMPUTE_COST_RELAY * 100))
|
||||
status_tab_data[++status_tab_data.len] = list("Bandwidth Progress: ", "[bandwith_status]%")
|
||||
status_tab_data[++status_tab_data.len] = list("Area Progress: ", "[turf_status]%")
|
||||
status_tab_data[++status_tab_data.len] = list("Total Relay Progress: ", "[(bandwith_status + turf_status) / 2]%")
|
||||
if(FLOCK_ENDGAME_RELAY_BUILT)
|
||||
status_tab_data[++status_tab_data.len] = list("Time until Broadcast: ", "[(flock.built_relay.started_time + flock.built_relay.win_time - world.time) / 10] second\s")
|
||||
|
||||
if(FLOCK_ENDGAME_RELAY_ACTIVATING, FLOCK_ENDGAME_VICTORY)
|
||||
status_tab_data[++status_tab_data.len] = list("Time until Broadcast: ", "!!! TRANSMITTING !!!")
|
||||
|
||||
/mob/living/basic/flock/drone/MouseDrop_T(mob/living/M, mob/living/user)
|
||||
. = ..()
|
||||
if(M != user || !istype(user, /mob/camera/flock))
|
||||
return
|
||||
|
||||
var/mob/camera/flock/ghost_bird = user
|
||||
if(istype(user) && ghost_bird.flock == flock)
|
||||
take_control(user)
|
||||
|
||||
/mob/living/basic/flock/drone/AltClick(mob/user, modifiers)
|
||||
. = ..()
|
||||
if(stat == DEAD)
|
||||
return
|
||||
var/mob/camera/flock/ghost_bird = user
|
||||
if(!istype(ghost_bird))
|
||||
return
|
||||
|
||||
var/list/choices = list()
|
||||
var/image/I = image('icons/hud/radial.dmi', "radial_use")
|
||||
choices["order"] = I
|
||||
|
||||
I = image('icons/hud/radial.dmi', "radial_control")
|
||||
choices["control"] = I
|
||||
|
||||
var/result = show_radial_menu(user, user, choices, "[ref(usr)]_flock_click", 40)
|
||||
switch(result)
|
||||
if("control")
|
||||
take_control(ghost_bird)
|
||||
|
||||
if("order")
|
||||
var/datum/action/cooldown/flock/control_drone/order_action = locate() in ghost_bird.actions
|
||||
if(order_action)
|
||||
if(order_action.selected_bird)
|
||||
order_action.unset_click_ability(usr)
|
||||
else
|
||||
order_action.set_click_ability(usr)
|
||||
order_action.Trigger(target = src)
|
||||
|
||||
/mob/living/basic/flock/drone/resolve_unarmed_attack(atom/attack_target, list/modifiers)
|
||||
if((a_intent == INTENT_GRAB || a_intent == INTENT_HARM) && active_part?.alt_attack)
|
||||
active_part?.right_click_on(attack_target, TRUE)
|
||||
else
|
||||
active_part?.left_click_on(attack_target, TRUE)
|
||||
|
||||
/mob/living/basic/flock/drone/RangedAttack(atom/A, modifiers)
|
||||
. = ..()
|
||||
if(.)
|
||||
return
|
||||
if((a_intent == INTENT_GRAB || a_intent == INTENT_HARM) && active_part?.alt_attack)
|
||||
active_part?.right_click_on(A, FALSE)
|
||||
else
|
||||
active_part?.left_click_on(A, FALSE)
|
||||
|
||||
/mob/living/basic/flock/update_health_hud()
|
||||
var/severity = 0
|
||||
var/healthpercent = ceil((health/maxHealth) * 100)
|
||||
if(healths) // to really put you in the boots of a basic mob
|
||||
var/atom/movable/screen/healths/flockdrone_health/HD = healths
|
||||
switch(healthpercent)
|
||||
if(100 to INFINITY)
|
||||
severity = 0
|
||||
if(81 to 99)
|
||||
severity = 1
|
||||
if(65 to 80)
|
||||
severity = 2
|
||||
if(49 to 64)
|
||||
severity = 3
|
||||
if(33 to 48)
|
||||
severity = 4
|
||||
if(17 to 32)
|
||||
severity = 5
|
||||
if(1 to 16)
|
||||
severity = 6
|
||||
else
|
||||
severity = 7
|
||||
|
||||
HD.icon_state = "health[severity]"
|
||||
|
||||
|
||||
|
||||
/mob/living/basic/flock/drone/harvest(mob/living/user)
|
||||
var/list/loot = list(
|
||||
/obj/item/stack/sheet/gnesis = 1,
|
||||
/obj/item/shard/gnesis_glass = 1,
|
||||
)
|
||||
|
||||
for(var/i in 3 to 6)
|
||||
var/path = pickweight(loot)
|
||||
new path(drop_location())
|
||||
|
||||
playsound(src, 'sound/effects/pylon_shatter.ogg', 30, TRUE, SILENCED_SOUND_EXTRARANGE)
|
||||
|
||||
// Spawn flock organs here
|
||||
qdel(src)
|
||||
|
||||
/mob/living/basic/flock/drone/get_flock_data()
|
||||
var/list/data = ..()
|
||||
var/current_behavior_name
|
||||
|
||||
if(controlled_by)
|
||||
data["task"] = "controlled"
|
||||
data["controller_ref"] = ref(controlled_by)
|
||||
|
||||
else if((ai_controller.ai_status == AI_ON) && length(ai_controller.current_behaviors))
|
||||
var/datum/ai_behavior/flock/current_task = ai_controller.current_behaviors[1]
|
||||
if(istype(current_task))
|
||||
current_behavior_name = current_task.name
|
||||
|
||||
data["task"] = current_behavior_name || "hibernating"
|
||||
return data
|
||||
|
||||
/mob/living/basic/flock/drone/on_ai_status_change(datum/ai_controller/source, ai_status)
|
||||
. = ..()
|
||||
if(ai_status == AI_STATUS_OFF && controlled_by)
|
||||
task_tag.set_text("Controlled By: [controlled_by.real_name]")
|
||||
|
||||
/mob/living/basic/flock/drone/dormantize()
|
||||
if(!flock)
|
||||
return ..()
|
||||
|
||||
if(controlled_by)
|
||||
release_control(FALSE)
|
||||
flock_talk(null, "Connection to drone [real_name] lost.", flock, involuntary = TRUE)
|
||||
|
||||
spawn(-1)
|
||||
say("error: out of signal range, disconnecting")
|
||||
return ..()
|
||||
|
||||
/// Sets the active flockdrone part.
|
||||
/mob/living/basic/flock/drone/proc/set_active_part(datum/flockdrone_part/new_part)
|
||||
var/datum/flockdrone_part/old_part = active_part
|
||||
active_part = new_part
|
||||
|
||||
active_part.screen_obj?.update_appearance()
|
||||
old_part?.screen_obj?.update_appearance()
|
||||
|
||||
/// Create all of the part datums for this mob.
|
||||
/mob/living/basic/flock/drone/proc/create_parts()
|
||||
parts += new /datum/flockdrone_part/converter(src)
|
||||
parts += new /datum/flockdrone_part/incapacitator(src)
|
||||
parts += new /datum/flockdrone_part/absorber(src)
|
||||
|
||||
/mob/living/basic/flock/drone/proc/start_flockphase()
|
||||
if(HAS_TRAIT(src, TRAIT_FLOCKPHASE))
|
||||
return FALSE
|
||||
|
||||
playsound(src, 'sound/goonstation/flockmind/flockdrone_floorrun.ogg', 30, TRUE, extrarange = SHORT_RANGE_SOUND_EXTRARANGE)
|
||||
|
||||
ADD_TRAIT(src, TRAIT_FLOCKPHASE, INNATE_TRAIT)
|
||||
pass_flags_self |= LETPASSTHROW | PASSFLOCK
|
||||
|
||||
mob_size = 0
|
||||
|
||||
set_density(FALSE)
|
||||
for(var/obj/item/grab/G in contents)
|
||||
qdel(G)
|
||||
move_speed -= 0.4
|
||||
|
||||
var/list/color_matrix = list(1,0,0, 0,1,0, 0,0,1, 0.15,0.77,0.66)
|
||||
var/matrix/shrink = matrix().Scale(0)
|
||||
animate(src, color = color_matrix, transform = shrink, time = 0.5 SECONDS, easing = SINE_EASING)
|
||||
return TRUE
|
||||
|
||||
/mob/living/basic/flock/drone/proc/stop_flockphase(force)
|
||||
if(!HAS_TRAIT(src, TRAIT_FLOCKPHASE))
|
||||
return FALSE
|
||||
|
||||
if(!force && avoid_stop_flockphase())
|
||||
return FALSE
|
||||
|
||||
playsound(src, 'sound/goonstation/flockmind/flockdrone_floorrun.ogg', 30, TRUE, extrarange = SHORT_RANGE_SOUND_EXTRARANGE)
|
||||
|
||||
REMOVE_TRAIT(src, TRAIT_FLOCKPHASE, INNATE_TRAIT)
|
||||
pass_flags_self &= ~(LETPASSTHROW | PASSFLOCK)
|
||||
mob_size = initial(mob_size)
|
||||
set_density(TRUE)
|
||||
move_speed += 0.4
|
||||
|
||||
animate(src, color = null, transform = null, time = 0.5 SECONDS, easing = SINE_EASING)
|
||||
|
||||
if(!isturf(loc))
|
||||
return
|
||||
|
||||
if(istype(loc, /turf/simulated/floor/flock))
|
||||
var/turf/simulated/floor/flock/flockfloor = loc
|
||||
LAZYREMOVE(flockfloor.flockrunning_mobs, src)
|
||||
flockfloor.update_power()
|
||||
|
||||
var/turf/turfloc = loc
|
||||
if(turfloc.can_flock_occupy(src))
|
||||
return
|
||||
|
||||
for(var/turf/T in get_adjacent_open_turfs(src))
|
||||
if(!T.can_flock_occupy(src))
|
||||
forceMove(T)
|
||||
break
|
||||
|
||||
/// Called in stop_flockphase() to attempt stopping flockphase due to being in a wall or something.
|
||||
/mob/living/basic/flock/drone/proc/avoid_stop_flockphase()
|
||||
if(!can_flockphase())
|
||||
return FALSE
|
||||
var/turf/T = loc
|
||||
|
||||
if(T.density)
|
||||
return TRUE
|
||||
|
||||
/// Returns TRUE if the drone can flockphase.
|
||||
/mob/living/basic/flock/drone/proc/can_flockphase()
|
||||
if(stat != CONSCIOUS)
|
||||
return FALSE
|
||||
|
||||
if(length(grabbed_by))
|
||||
return FALSE
|
||||
|
||||
if(!substrate.has_points())
|
||||
return FALSE
|
||||
|
||||
return TRUE
|
||||
|
||||
/// Deducts the substrate tax for flockphasing, ending flockphase if the drone ran out of points.
|
||||
/mob/living/basic/flock/drone/proc/flockphase_tax()
|
||||
substrate.remove_points(1)
|
||||
if(!substrate.has_points())
|
||||
stop_flockphase(TRUE)
|
||||
return FALSE
|
||||
return TRUE
|
||||
|
||||
/mob/living/basic/flock/drone/proc/take_control(mob/camera/flock/master_bird)
|
||||
if(HAS_TRAIT_FROM(src, TRAIT_AI_DISABLE_PLANNING, FLOCK_CONTROLLED_BY_OVERMIND_SOURCE))
|
||||
to_chat(master_bird, SPAN_ALERT("This drone is recieving a sentient-level instruction."))
|
||||
return FALSE
|
||||
|
||||
if(controlled_by)
|
||||
to_chat(master_bird, SPAN_ALERT("This drone is already under another partition's command."))
|
||||
return FALSE
|
||||
|
||||
stop_flockphase()
|
||||
|
||||
controlled_by = master_bird
|
||||
controlled_by.controlling_bird = src
|
||||
|
||||
if(controlled_by.mind)
|
||||
key = controlled_by.key
|
||||
mind = controlled_by.mind
|
||||
|
||||
if(isflocktrace(controlled_by))
|
||||
flock.add_notice(src, FLOCK_NOTICE_FLOCKTRACE_CONTROL)
|
||||
else
|
||||
flock.add_notice(src, FLOCK_NOTICE_FLOCKMIND_CONTROL)
|
||||
|
||||
to_chat(src, SPAN_FLOCKSAY("<b>\[SYSTEM: Control of drone [real_name] established.\]</b>"))
|
||||
return TRUE
|
||||
|
||||
/mob/living/basic/flock/drone/proc/release_control(go_dormant = FALSE)
|
||||
if(isnull(controlled_by))
|
||||
return
|
||||
|
||||
task_tag.set_text("")
|
||||
|
||||
var/dest_was_safe = TRUE
|
||||
var/turf/destination = get_turf(src)
|
||||
if(!flock.is_on_safe_z(destination))
|
||||
dest_was_safe = FALSE
|
||||
destination = get_turf(pick(flock.drones)) || get_safe_random_station_turf()
|
||||
|
||||
var/mob/camera/flock/master_bird = controlled_by
|
||||
controlled_by = null
|
||||
|
||||
if(flock)
|
||||
flock.remove_notice(src, FLOCK_NOTICE_FLOCKMIND_CONTROL)
|
||||
flock.remove_notice(src, FLOCK_NOTICE_FLOCKTRACE_CONTROL)
|
||||
|
||||
if(isnull(master_bird) && ckey)
|
||||
if(flock)
|
||||
master_bird = new /mob/camera/flock/trace(destination, flock)
|
||||
else
|
||||
ghostize(FALSE)
|
||||
|
||||
if(!master_bird)
|
||||
return
|
||||
|
||||
master_bird.forceMove(destination)
|
||||
if(mind)
|
||||
master_bird.key = key
|
||||
master_bird.mind = mind
|
||||
|
||||
flock_talk(null, "Control of [real_name] surrendered.", flock, involuntary = TRUE)
|
||||
if(!dest_was_safe)
|
||||
to_chat(master_bird, SPAN_WARNING("You feel your consciousness weaking as you are ripped further from your rift, and you retreat back to safety."))
|
||||
|
||||
if(!flock && go_dormant)
|
||||
dormantize()
|
||||
|
||||
/mob/living/basic/flock/drone/proc/split_into_bits()
|
||||
ai_controller.pause_ai(3 SECONDS)
|
||||
say("\[System notification: drone diffracting.\]")
|
||||
flock?.free_unit(src)
|
||||
|
||||
var/turf/T = get_turf(src)
|
||||
for(var/i in 1 to 3)
|
||||
var/mob/living/basic/flock/bit/bitty_bird = new(T, flock)
|
||||
bitty_bird.i_just_split(T)
|
||||
|
||||
var/list/new_color = list(1,0,0, 0,1,0, 0,0,1, 0.15,0.77,0.66)
|
||||
color = null
|
||||
animate(src, color=new_color, alpha = 0, time = 0.5 SECONDS)
|
||||
|
||||
addtimer(CALLBACK(src, PROC_REF(finish_splitting)), 0.5 SECONDS)
|
||||
|
||||
/mob/living/basic/flock/drone/proc/finish_splitting()
|
||||
qdel(src)
|
||||
@@ -0,0 +1,95 @@
|
||||
/datum/flockdrone_part/absorber
|
||||
var/obj/item/held_item
|
||||
|
||||
/// Damage rate
|
||||
var/absorption_rate = 4
|
||||
/// Per point of integrity, generate this much substrate.
|
||||
var/integrity_substrate_ratio = 2
|
||||
|
||||
/datum/flockdrone_part/absorber/Destroy(force, ...)
|
||||
QDEL_NULL(held_item)
|
||||
return ..()
|
||||
|
||||
/datum/flockdrone_part/absorber/left_click_on(atom/target, in_reach)
|
||||
var/obj/item/I = target
|
||||
if(!in_reach || !istype(I) || !isturf(I.loc))
|
||||
return
|
||||
|
||||
return try_pickup_item(I)
|
||||
|
||||
/datum/flockdrone_part/absorber/process(seconds_per_tick)
|
||||
var/added = 0
|
||||
var/obj/item/eating = held_item
|
||||
|
||||
if(istype(eating, /obj/item/flock_cube))
|
||||
var/obj/item/flock_cube/cube = eating
|
||||
added = cube.substrate
|
||||
drone.substrate.add_points(added)
|
||||
qdel(cube)
|
||||
to_chat(drone, SPAN_NOTICE("We decompile the resource cache, adding <b>[added]</b> substrate to our reserves."))
|
||||
else
|
||||
added = eating.take_damage(absorption_rate * seconds_per_tick, BRUTE, ACID, sound_effect = FALSE, armor_penetration_percentage = 100)
|
||||
drone.substrate.add_points(added * integrity_substrate_ratio)
|
||||
|
||||
if(!held_item) // if take_damage qdeletes it, it becomes null due to signal stuff.
|
||||
to_chat(drone, SPAN_NOTICE("We finish converting [eating] into substrate."))
|
||||
return
|
||||
|
||||
playsound(drone, "sparks", 30, TRUE, extrarange = SILENCED_SOUND_EXTRARANGE)
|
||||
|
||||
/datum/flockdrone_part/absorber/proc/try_pickup_item(obj/item/I)
|
||||
if(held_item)
|
||||
return FALSE
|
||||
|
||||
if(I.flags & ABSTRACT)
|
||||
return FALSE
|
||||
|
||||
drone.face_atom(I)
|
||||
I.forceMove(drone)
|
||||
if(QDELETED(I))
|
||||
return FALSE
|
||||
|
||||
held_item = I
|
||||
RegisterSignal(I, list(COMSIG_MOVABLE_MOVED, COMSIG_PARENT_QDELETING), PROC_REF(held_item_moved))
|
||||
START_PROCESSING(SSprocessing, src)
|
||||
|
||||
var/matrix/first_matrix = matrix()
|
||||
first_matrix.Turn(-45)
|
||||
first_matrix.Scale(1.2, 0.6)
|
||||
var/matrix/second_matrix = matrix()
|
||||
first_matrix.Turn(45)
|
||||
first_matrix.Scale(0.6, 1.2)
|
||||
animate(held_item, loop= -1, color="#00ffd7", transform= first_matrix, time = 2 SECONDS, tag = "flockdrone_eat")
|
||||
animate(color = "#ffffff", transform = second_matrix, time = 2 SECONDS)
|
||||
|
||||
screen_obj?.vis_contents += held_item
|
||||
return TRUE
|
||||
|
||||
/datum/flockdrone_part/absorber/proc/try_drop_item(move = TRUE)
|
||||
if(!held_item)
|
||||
return FALSE
|
||||
|
||||
UnregisterSignal(held_item, list(COMSIG_MOVABLE_MOVED, COMSIG_PARENT_QDELETING))
|
||||
|
||||
var/obj/item/old_item = held_item
|
||||
animate(held_item, tag = "flockdrone_eat")
|
||||
held_item = null
|
||||
|
||||
STOP_PROCESSING(SSprocessing, src)
|
||||
screen_obj?.vis_contents -= old_item
|
||||
|
||||
if(!move)
|
||||
return TRUE
|
||||
|
||||
var/turf/drop_loc = drone.drop_location()
|
||||
if(drop_loc)
|
||||
old_item.forceMove(drop_loc)
|
||||
else
|
||||
qdel(old_item)
|
||||
|
||||
return TRUE
|
||||
|
||||
/datum/flockdrone_part/absorber/proc/held_item_moved(datum/source)
|
||||
SIGNAL_HANDLER
|
||||
|
||||
try_drop_item(FALSE)
|
||||
@@ -0,0 +1,36 @@
|
||||
/datum/flockdrone_part/converter
|
||||
alt_attack = TRUE
|
||||
|
||||
/datum/flockdrone_part/converter/left_click_on(atom/target, in_reach)
|
||||
if(!in_reach)
|
||||
return
|
||||
|
||||
if(isliving(target))
|
||||
return try_cage(target)
|
||||
|
||||
if(istype(target, /obj/structure/flock/tealprint))
|
||||
var/datum/action/cooldown/flock/deposit/deposit_action = locate() in drone.actions
|
||||
return deposit_action.Trigger(target = target)
|
||||
|
||||
if(istype(target, /obj/structure/closet))
|
||||
var/obj/structure/closet/C = target
|
||||
C.toggle()
|
||||
|
||||
var/turf/T = get_turf(target)
|
||||
var/datum/action/cooldown/flock/convert/convert_action = locate() in drone.actions
|
||||
return convert_action.Trigger(target = T)
|
||||
|
||||
/datum/flockdrone_part/converter/right_click_on(atom/target, in_reach)
|
||||
if(!in_reach)
|
||||
return
|
||||
|
||||
var/datum/action/cooldown/flock/deconstruct/decon_action = locate() in drone.actions
|
||||
return decon_action.Trigger(target = target)
|
||||
|
||||
/datum/flockdrone_part/converter/proc/try_cage(mob/living/victim)
|
||||
if(isflockmob(victim))
|
||||
to_chat(drone, SPAN_WARNING("ERROR: Unable to imprison substrate construct."))
|
||||
return FALSE
|
||||
|
||||
var/datum/action/cooldown/flock/cage_mob/cage_action = locate() in drone.actions
|
||||
return cage_action.Trigger(target = victim)
|
||||
@@ -0,0 +1,26 @@
|
||||
/datum/flockdrone_part
|
||||
var/mob/living/basic/flock/drone/drone
|
||||
/// Reference to the associated screen object, if it exists (grr mobs that havent had a player connected)
|
||||
var/atom/movable/screen/flockdrone_part/screen_obj
|
||||
/// If the part has an alt-attack
|
||||
var/alt_attack = FALSE
|
||||
|
||||
/datum/flockdrone_part/New(drone)
|
||||
src.drone = drone
|
||||
|
||||
/datum/flockdrone_part/Destroy(force, ...)
|
||||
screen_obj?.part_ref = null
|
||||
screen_obj = null
|
||||
drone = null
|
||||
return ..()
|
||||
|
||||
/datum/flockdrone_part/proc/is_active()
|
||||
return drone.active_part == src
|
||||
|
||||
/// Called when a drone with this part active left clicks on an atom. in_reach is TRUE if the target atom is reachable.
|
||||
/datum/flockdrone_part/proc/left_click_on(atom/target, in_reach)
|
||||
return
|
||||
|
||||
/// Called when a drone with this part active right clicks on an atom. in_reach is TRUE if the target atom is reachable.
|
||||
/datum/flockdrone_part/proc/right_click_on(atom/target, in_reach)
|
||||
return
|
||||
@@ -0,0 +1,54 @@
|
||||
/datum/flockdrone_part/incapacitator
|
||||
/// Maximum shots that can be stored
|
||||
var/max_shots = 5
|
||||
|
||||
/// How many shots we have left
|
||||
var/shot_count = 5
|
||||
|
||||
/// Time between shots cooldown
|
||||
COOLDOWN_DECLARE(shoot_cd)
|
||||
/// Time between shots
|
||||
var/time_between_shots = 1.2 SECONDS
|
||||
|
||||
/// Time to gain 1 charge.
|
||||
var/recharge_time = 4 SECONDS
|
||||
|
||||
var/recharge_timer_id
|
||||
|
||||
/datum/flockdrone_part/incapacitator/left_click_on(atom/target, in_reach)
|
||||
if(!COOLDOWN_FINISHED(src, shoot_cd))
|
||||
return FALSE
|
||||
|
||||
if(!shot_count)
|
||||
return FALSE
|
||||
|
||||
if(istype(target, /atom/movable/screen))
|
||||
return FALSE
|
||||
|
||||
shot_count -= 1
|
||||
screen_obj?.update_appearance()
|
||||
|
||||
var/obj/projectile/energy/flock_bolt/bolt = new(get_turf(drone))
|
||||
bolt.preparePixelProjectile(target, drone)
|
||||
bolt.firer = drone
|
||||
bolt.firer_source_atom = src
|
||||
bolt.def_zone = drone.zone_selected
|
||||
bolt.fire()
|
||||
playsound(drone, 'sound/weapons/resonator_fire.ogg', 50, TRUE)
|
||||
|
||||
if(!recharge_timer_id)
|
||||
recharge_timer_id = addtimer(CALLBACK(src, PROC_REF(recharge)), recharge_time)
|
||||
|
||||
COOLDOWN_START(src, shoot_cd, time_between_shots)
|
||||
return TRUE
|
||||
|
||||
/// Increments shot count by 1 and starts the recharge timer if necessary.
|
||||
/datum/flockdrone_part/incapacitator/proc/recharge()
|
||||
shot_count = min(shot_count + 1, max_shots)
|
||||
screen_obj?.update_appearance()
|
||||
|
||||
if(shot_count == max_shots)
|
||||
recharge_timer_id = null
|
||||
else
|
||||
recharge_timer_id = addtimer(CALLBACK(src, PROC_REF(recharge)), recharge_time)
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
/datum/objective/flock_relay
|
||||
name = "Broadcast"
|
||||
explanation_text = "Broadcast the Signal."
|
||||
|
||||
/datum/objective/flock_relay/check_completion()
|
||||
var/mob/camera/flock/ghostbird = owner.current
|
||||
return ghostbird.flock.flock_game_status == FLOCK_ENDGAME_VICTORY
|
||||
@@ -0,0 +1,13 @@
|
||||
/mob/camera/flock/say(message)
|
||||
if(!message)
|
||||
return
|
||||
flock_talk(src, message, flock)
|
||||
|
||||
/proc/flock_talk(atom/speaker, message, datum/flock/flock, involuntary = FALSE)
|
||||
|
||||
message = trim(copytext_char(sanitize(message), 1, MAX_MESSAGE_LEN))
|
||||
if(!message)
|
||||
return
|
||||
|
||||
var/datum/language/flock/symphonic = GLOB.all_languages["Symphonic"]
|
||||
symphonic.broadcast(speaker, message, involuntary = involuntary)
|
||||
@@ -0,0 +1,228 @@
|
||||
/obj/structure/flock
|
||||
name = "CALL A CODER AAAAAAAAAA"
|
||||
desc = ABSTRACT_TYPE_DESC
|
||||
icon = 'icons/goonstation/mob/featherzone.dmi'
|
||||
anchored = TRUE
|
||||
density = TRUE
|
||||
|
||||
max_integrity = 50
|
||||
|
||||
armor = list(MELEE = -20, BULLET = -20, LASER = 80, ENERGY = 80, BOMB = 0, RAD = 100, FIRE = 80, ACID = 100)
|
||||
|
||||
var/datum/flock/flock
|
||||
|
||||
/// Info tag for the flock name of the structure.
|
||||
var/obj/effect/abstract/info_tag/flock/name_tag
|
||||
|
||||
/// Info tag for the actual information of the structure.
|
||||
var/obj/effect/abstract/info_tag/flock/info/info_tag
|
||||
|
||||
var/flock_id
|
||||
/// Shown in the flockmind UI
|
||||
var/flock_desc
|
||||
|
||||
/// How much juice it takes to construct
|
||||
var/resource_cost = 0
|
||||
/// How long it takes to build
|
||||
var/build_time = 0
|
||||
/// Is the tealprint cancellable
|
||||
var/cancellable = TRUE
|
||||
/// Is the structure finished?
|
||||
var/fully_built = FALSE
|
||||
/// If TRUE, flockdrones cannot deconstruct this.
|
||||
var/no_flock_decon = FALSE
|
||||
|
||||
/// world.time this was created at
|
||||
var/spawn_time
|
||||
/// How much bandwidth this structure provides to the Flock.
|
||||
var/bandwidth_provided = 0
|
||||
|
||||
/// Whether or not the turret is active. The state of the Flock can change this.
|
||||
var/active = FALSE
|
||||
/// The bandwidth cost while active
|
||||
var/active_bandwidth_cost = 0
|
||||
|
||||
/// Allows flockdrones to pass through.
|
||||
var/allow_flockpass = TRUE
|
||||
|
||||
COOLDOWN_DECLARE(scream_cd)
|
||||
|
||||
/obj/structure/flock/Initialize(mapload, datum/flock/join_flock)
|
||||
. = ..()
|
||||
|
||||
spawn_time = world.time
|
||||
|
||||
join_flock ||= get_default_flock()
|
||||
if(join_flock)
|
||||
join_flock.add_structure(src)
|
||||
AddComponent(/datum/component/flock_object, join_flock)
|
||||
|
||||
var/static/list/loc_connections = list(
|
||||
COMSIG_ATOM_ENTERED = PROC_REF(on_crossed),
|
||||
)
|
||||
AddElement(/datum/element/connect_loc, loc_connections)
|
||||
|
||||
if(build_time)
|
||||
START_PROCESSING(SSobj, src)
|
||||
else
|
||||
finish_building()
|
||||
|
||||
ADD_TRAIT(src, TRAIT_FLOCK_EXAMINE, INNATE_TRAIT)
|
||||
if(no_flock_decon)
|
||||
ADD_TRAIT(src, TRAIT_FLOCK_NODECON, INNATE_TRAIT)
|
||||
|
||||
name_tag = new()
|
||||
name_tag.set_parent(src)
|
||||
name_tag.set_text(flock_id)
|
||||
|
||||
info_tag = new()
|
||||
info_tag.set_parent(src)
|
||||
return INITIALIZE_HINT_LATELOAD
|
||||
|
||||
/obj/structure/flock/LateInitialize()
|
||||
. = ..()
|
||||
update_info_tag()
|
||||
|
||||
/obj/structure/flock/Destroy()
|
||||
STOP_PROCESSING(SSobj, src)
|
||||
flock?.free_structure(src)
|
||||
QDEL_NULL(name_tag)
|
||||
QDEL_NULL(info_tag)
|
||||
return ..()
|
||||
|
||||
/obj/structure/flock/get_flock_id()
|
||||
return flock_id
|
||||
|
||||
/obj/structure/flock/proc/get_flock_data()
|
||||
. = list()
|
||||
.["ref"] = ref(src)
|
||||
.["name"] = flock_id
|
||||
.["health"] = get_integrity_percentage()
|
||||
.["compute"] = active ? -active_bandwidth_cost : bandwidth_provided
|
||||
.["desc"] = flock_desc
|
||||
.["area"] = get_area_name(src, TRUE) || "???"
|
||||
|
||||
/obj/structure/flock/deconstruct(disassembled)
|
||||
visible_message(SPAN_WARNING("[src] dissolves into nothingness."))
|
||||
var/refund = round(get_integrity_percentage() * (disassembled ? 1 : 0.5) * resource_cost, 1)
|
||||
if(refund)
|
||||
var/obj/item/flock_cube/cube = new(drop_location())
|
||||
cube.substrate = refund
|
||||
|
||||
return ..()
|
||||
|
||||
/obj/structure/flock/CanPass(atom/movable/mover, border_dir)
|
||||
. = ..()
|
||||
if(allow_flockpass)
|
||||
return isflockdrone(mover)
|
||||
|
||||
/obj/structure/flock/CanPathfindPass(to_dir, datum/can_pass_info/pass_info)
|
||||
var/atom/M = locateUID(pass_info.caller_uid)
|
||||
if(!M)
|
||||
return ..()
|
||||
if(allow_flockpass)
|
||||
return isflockdrone(M)
|
||||
return ..()
|
||||
|
||||
/obj/structure/flock/try_flock_convert(datum/flock/flock, force)
|
||||
return
|
||||
|
||||
/obj/structure/flock/attack_hand(mob/living/user, list/modifiers)
|
||||
. = ..()
|
||||
if(.)
|
||||
return
|
||||
|
||||
if(user.a_intent == INTENT_HARM)
|
||||
user.visible_message(SPAN_DANGER("<b>[user]</b> punches <b>[src]."))
|
||||
user.do_attack_animation(src, ATTACK_EFFECT_PUNCH)
|
||||
//playsound(src, 'sound/impact_sounds/Crystal_Hit_1.ogg', 50, TRUE, -1)
|
||||
bitch_and_moan()
|
||||
take_damage(1, BRUTE)
|
||||
return TRUE
|
||||
|
||||
/obj/structure/flock/attacked_by(obj/item/attacking_item, mob/living/user)
|
||||
. = ..()
|
||||
if(!.)
|
||||
return
|
||||
|
||||
bitch_and_moan()
|
||||
// if(. < 5)
|
||||
// playsound(src, 'sound/impact_sounds/Crystal_Hit_1.ogg', 50, TRUE)
|
||||
// else
|
||||
// playsound(src, 'sound/impact_sounds/Glass_Shards_Hit_1.ogg', 50, TRUE)
|
||||
|
||||
|
||||
/obj/structure/flock/process()
|
||||
update_info_tag()
|
||||
if(spawn_time + build_time <= world.time)
|
||||
finish_building()
|
||||
return
|
||||
|
||||
/// Returns the number of seconds remaining for the build.
|
||||
/obj/structure/flock/proc/build_time_left()
|
||||
return ((spawn_time + build_time) - world.time) / 10
|
||||
|
||||
/obj/structure/flock/proc/set_active(new_state)
|
||||
if(active == new_state)
|
||||
return
|
||||
|
||||
active = new_state
|
||||
update_appearance()
|
||||
|
||||
if(active)
|
||||
flock.remove_bandwidth_influence(bandwidth_provided)
|
||||
flock.add_bandwidth_influence(-active_bandwidth_cost)
|
||||
else
|
||||
flock.remove_bandwidth_influence(-active_bandwidth_cost)
|
||||
flock.add_bandwidth_influence(bandwidth_provided)
|
||||
|
||||
return new_state
|
||||
|
||||
/// Called when an object finishes construction
|
||||
/obj/structure/flock/proc/finish_building()
|
||||
SHOULD_CALL_PARENT(TRUE)
|
||||
STOP_PROCESSING(SSobj, src)
|
||||
fully_built = TRUE
|
||||
|
||||
/obj/structure/flock/examine(mob/user)
|
||||
if(!isflockmob(user))
|
||||
return ..()
|
||||
|
||||
. = list(
|
||||
SPAN_FLOCKSAY("<b>###=- Ident confirmed, data packet received.</b>"),
|
||||
SPAN_FLOCKSAY("<b>ID:</b> [get_flock_id()]"),
|
||||
SPAN_FLOCKSAY("<b>Flock:</b> [flock?.name || "N/A"]"),
|
||||
SPAN_FLOCKSAY("<b>System Integrity:</b> [get_integrity_percentage()]%"),
|
||||
)
|
||||
|
||||
if(!fully_built)
|
||||
. += SPAN_FLOCKSAY("<b>Time Left:</b> [build_time_left()] seconds")
|
||||
|
||||
var/list/additional_lines = flock_structure_examine(user)
|
||||
if(length(additional_lines))
|
||||
. += additional_lines
|
||||
|
||||
. += SPAN_FLOCKSAY("<b>###=-</b>")
|
||||
|
||||
/obj/structure/flock/proc/flock_structure_examine(mob/user)
|
||||
return
|
||||
|
||||
/// Stub for children to set their info in process() and initialize()
|
||||
/obj/structure/flock/proc/update_info_tag()
|
||||
return
|
||||
|
||||
/obj/structure/flock/proc/on_crossed(atom/source, atom/movable/crosser)
|
||||
SIGNAL_HANDLER
|
||||
|
||||
if(!allow_flockpass || !isflockdrone(crosser))
|
||||
return
|
||||
|
||||
if(!HAS_TRAIT(crosser, TRAIT_FLOCKPHASE))
|
||||
animate_flockpass(crosser)
|
||||
|
||||
/obj/structure/flock/proc/bitch_and_moan()
|
||||
if(!COOLDOWN_FINISHED(src, scream_cd))
|
||||
return
|
||||
|
||||
COOLDOWN_START(src, scream_cd, 10 SECONDS)
|
||||
flock_talk(src, "WARNING: Under attack", flock)
|
||||
@@ -0,0 +1,272 @@
|
||||
/obj/structure/flock/cage
|
||||
name = "energy cage"
|
||||
desc = "A digitized energy structure that appears to turn matter into gnesis."
|
||||
icon_state = "cage"
|
||||
alpha = 190
|
||||
|
||||
anchored = FALSE
|
||||
max_integrity = 30
|
||||
|
||||
flock_desc = "Converts organic creatures into Flockdrones."
|
||||
flock_id = "Matter reprocessor"
|
||||
|
||||
var/tmp/mob/living/victim
|
||||
/// Current item being munched on.
|
||||
var/tmp/obj/item/eating
|
||||
|
||||
/// With this much gnesis, create an egg.
|
||||
var/egg_gnesis_cost = 100
|
||||
/// Rate of damage to items being consumed by the cage
|
||||
var/absorption_rate = 4
|
||||
/// Per point of damage, generate this much substrate.
|
||||
var/integrity_substrate_ratio = 2
|
||||
/// Timer until you can ghost from your body without penalty
|
||||
var/ghost_timer
|
||||
|
||||
COOLDOWN_DECLARE(flock_message_cd)
|
||||
COOLDOWN_DECLARE(relaymove_cd)
|
||||
|
||||
/obj/structure/flock/cage/Initialize(mapload, datum/flock/join_flock)
|
||||
. = ..()
|
||||
create_reagents(200)
|
||||
AddComponent(/datum/component/flock_protection)
|
||||
|
||||
/obj/structure/flock/cage/Destroy()
|
||||
QDEL_NULL(victim)
|
||||
QDEL_NULL(eating)
|
||||
return ..()
|
||||
|
||||
/obj/structure/flock/cage/deconstruct(disassembled)
|
||||
if(disassembled)
|
||||
spend_gnesis(TRUE)
|
||||
else
|
||||
reagents.reaction(get_turf(src), REAGENT_TOUCH)
|
||||
|
||||
if(victim)
|
||||
visible_message(SPAN_WARNING("[victim] breaks free from [src]."))
|
||||
set_victim(null)
|
||||
|
||||
var/drop_loc = drop_location()
|
||||
for(var/atom/movable/AM as anything in contents)
|
||||
AM.forceMove(drop_loc)
|
||||
|
||||
. = ..()
|
||||
|
||||
/obj/structure/flock/cage/process(seconds_per_tick)
|
||||
spend_gnesis()
|
||||
|
||||
if(victim && flock)
|
||||
flock.update_enemy(victim)
|
||||
|
||||
if(QDELETED(eating))
|
||||
eating = null
|
||||
|
||||
if(!eating)
|
||||
var/obj/item/edibles = list()
|
||||
for(var/obj/item/edible in src)
|
||||
edibles += edible
|
||||
|
||||
if(length(edibles))
|
||||
set_eating_target(pick(edibles))
|
||||
playsound(src, 'sound/goonstation/weapons/nano-blade-1.ogg', 50, TRUE)
|
||||
|
||||
if(victim?.stat == CONSCIOUS)
|
||||
to_chat(victim, SPAN_WARNING("[eating] begins to melt away."))
|
||||
|
||||
else
|
||||
chew_on_mob(seconds_per_tick)
|
||||
|
||||
else
|
||||
var/added = eating.take_damage(absorption_rate * seconds_per_tick * 25, BRUTE, armor_penetration_percentage = 100)
|
||||
reagents.add_reagent(/datum/reagent/gnesis, integrity_substrate_ratio * added)
|
||||
|
||||
if(victim && COOLDOWN_FINISHED(src, flock_message_cd))
|
||||
COOLDOWN_START(src, flock_message_cd, rand(10, 25) SECONDS)
|
||||
to_chat(victim, SPAN_FLOCKSAY("<i>[pick(strings("flock.json", "conversion"))]</i>"))
|
||||
|
||||
if(!victim) // Victim gibbed
|
||||
deconstruct(TRUE)
|
||||
|
||||
/obj/structure/flock/cage/container_resist(mob/living/user)
|
||||
to_chat(victim, SPAN_WARNING("You resist against the confines of the cage!"))
|
||||
if(!do_after_once(user, 3 SECONDS, FALSE, src, interaction_key = "flock_cage_resist"))
|
||||
return
|
||||
|
||||
audible_message("[src] [pick("cracks","bends","shakes","groans")].")
|
||||
playsound(
|
||||
src,
|
||||
pick('sound/goonstation/flockmind/flockdrone_grump1.ogg', 'sound/goonstation/flockmind/flockdrone_grump2.ogg', 'sound/goonstation/flockmind/flockdrone_grump3.ogg'),
|
||||
50,
|
||||
TRUE
|
||||
)
|
||||
take_damage(7, BRUTE)
|
||||
|
||||
/obj/structure/flock/cage/relaymove(mob/living/user, direction)
|
||||
if(!COOLDOWN_FINISHED(src, relaymove_cd))
|
||||
return
|
||||
|
||||
COOLDOWN_START(src, relaymove_cd, 1 SECONDS)
|
||||
|
||||
if(!prob(80))
|
||||
return
|
||||
|
||||
if(prob(20))
|
||||
audible_message("[src] [pick("cracks", "bends", "shakes", "groans")].")
|
||||
|
||||
take_damage(1, BRUTE)
|
||||
|
||||
/obj/structure/flock/cage/flock_structure_examine(mob/user)
|
||||
return list(
|
||||
SPAN_FLOCKSAY("<b>Volume:</b> [reagents.get_reagent_amount(/datum/reagent/gnesis)]"),
|
||||
SPAN_FLOCKSAY("<b>Needed volume:</b> [egg_gnesis_cost]<br>"),
|
||||
)
|
||||
|
||||
/// Picks an item, organ, or bodypart, to munch on.
|
||||
/obj/structure/flock/cage/proc/chew_on_mob(seconds_per_tick)
|
||||
if(!ishuman(victim))
|
||||
victim.adjustBruteLoss(absorption_rate * seconds_per_tick)
|
||||
if(victim.stat == DEAD)
|
||||
visible_message(SPAN_DANGER("[src] rips apart what remains of [victim]."))
|
||||
victim.gib()
|
||||
set_victim(null)
|
||||
return
|
||||
|
||||
var/mob/living/carbon/human/human_victim = victim
|
||||
var/list/items = human_victim.get_equipped_items()
|
||||
if(length(items))
|
||||
while(length(items) && !eating)
|
||||
var/obj/item/candidate = pick_n_take(items)
|
||||
if(candidate.resistance_flags & INDESTRUCTIBLE)
|
||||
human_victim.drop_item_to_ground(candidate, TRUE, TRUE)
|
||||
visible_message(SPAN_WARNING("[src] pulls [eating] from [human_victim] and drops it!"))
|
||||
continue
|
||||
human_victim.transfer_item_to(candidate, src, TRUE, TRUE)
|
||||
if(!QDELETED(candidate))
|
||||
set_eating_target(candidate)
|
||||
|
||||
if(eating)
|
||||
visible_message(SPAN_WARNING("[src] pulls [eating] from [human_victim] and begins ripping it apart."))
|
||||
playsound(src, 'sound/goonstation/weapons/nano-blade-1.ogg', 50, TRUE)
|
||||
return
|
||||
|
||||
var/list/bodyparts = human_victim.bodyparts.Copy()
|
||||
for(var/obj/item/organ/external/BP in bodyparts)
|
||||
if(BP.body_part in list(HEAD, UPPER_TORSO, LOWER_TORSO))
|
||||
bodyparts -= BP
|
||||
|
||||
if(length(bodyparts))
|
||||
var/obj/item/organ/external/yummy_appendage = pick(bodyparts)
|
||||
|
||||
human_victim.pain(yummy_appendage.name, 85)
|
||||
yummy_appendage.droplimb(FALSE)
|
||||
set_eating_target(yummy_appendage)
|
||||
eating.forceMove(src)
|
||||
|
||||
visible_message(SPAN_DANGER("[src] tears [eating] from [human_victim] and begins ripping it apart."))
|
||||
return
|
||||
|
||||
var/list/skip_organs = list("eyes", "ears", "brain", "heart", "lungs")
|
||||
for(var/obj/item/organ/internal/O in bodyparts)
|
||||
if(O.slot in skip_organs)
|
||||
bodyparts -= O
|
||||
|
||||
if(length(bodyparts))
|
||||
var/obj/item/organ/internal/yummy_organ = pick(bodyparts)
|
||||
var/organ_loc_str = yummy_organ.parent_organ
|
||||
human_victim.pain(organ_loc_str, 85)
|
||||
yummy_organ.remove(human_victim)
|
||||
if(!QDELING(yummy_organ))
|
||||
set_eating_target(yummy_organ)
|
||||
eating.forceMove(src)
|
||||
|
||||
visible_message(SPAN_DANGER("[src] tears [eating] from [human_victim]'s [organ_loc_str] and begins ripping it apart."))
|
||||
return
|
||||
|
||||
visible_message(SPAN_DANGER("[src] rips apart what remains of [human_victim]."))
|
||||
set_victim(null)
|
||||
human_victim.gib(TRUE, TRUE, TRUE)
|
||||
|
||||
// INTO THE CAGE
|
||||
/obj/structure/flock/cage/proc/cage_mob(mob/living/L)
|
||||
L.forceMove(src)
|
||||
set_victim(L)
|
||||
victim.visible_message(SPAN_DANGER("A [name] materializes around [victim],"))
|
||||
ghost_timer = addtimer(CALLBACK(src, PROC_REF(ghost_check), L), 15 SECONDS, TIMER_UNIQUE|TIMER_STOPPABLE)
|
||||
|
||||
/// Spends gnesis on eggs or a cube. Only spends on eggs by default.
|
||||
/obj/structure/flock/cage/proc/spend_gnesis(all = FALSE)
|
||||
var/egg_spawn_count = 0
|
||||
var/spend_on_cube = 0
|
||||
|
||||
// Get egg count and spend the gnesis.
|
||||
while(reagents.has_reagent(/datum/reagent/gnesis, egg_gnesis_cost))
|
||||
reagents.remove_reagent(/datum/reagent/gnesis, egg_gnesis_cost)
|
||||
egg_spawn_count++
|
||||
|
||||
// Spawn eggs or pool it to the cube
|
||||
for(var/i in 1 to egg_spawn_count)
|
||||
if(length(flock?.drones) <= FLOCK_DRONE_LIMIT)
|
||||
var/obj/structure/flock/egg/egg = new(drop_location(), flock)
|
||||
egg.throw_at(get_edge_target_turf(egg, pick(GLOB.alldirs)), 6, 3)
|
||||
else
|
||||
spend_on_cube += egg_gnesis_cost
|
||||
|
||||
// If we're dumping it all, collect the remaining gnesis
|
||||
if(all)
|
||||
spend_on_cube += reagents.get_reagent_amount(/datum/reagent/gnesis)
|
||||
|
||||
// Cube
|
||||
if(spend_on_cube && all)
|
||||
reagents.remove_reagent(/datum/reagent/gnesis, spend_on_cube)
|
||||
|
||||
var/obj/item/flock_cube/cube = new
|
||||
cube.substrate = spend_on_cube
|
||||
cube.forceMove(drop_location())
|
||||
|
||||
/// Setter for eating, no side effects.
|
||||
/obj/structure/flock/cage/proc/set_eating_target(obj/item/new_eating)
|
||||
if(eating)
|
||||
UnregisterSignal(eating, COMSIG_MOVABLE_MOVED)
|
||||
|
||||
eating = new_eating
|
||||
|
||||
if(!eating)
|
||||
return
|
||||
|
||||
RegisterSignal(eating, COMSIG_MOVABLE_MOVED, PROC_REF(target_gone))
|
||||
|
||||
/// Setter for victim, no side effects.
|
||||
/obj/structure/flock/cage/proc/set_victim(mob/living/new_victim)
|
||||
if(victim)
|
||||
UnregisterSignal(victim, COMSIG_MOVABLE_MOVED)
|
||||
victim.clear_fullscreen("flock_convert")
|
||||
|
||||
victim = new_victim
|
||||
|
||||
if(!victim)
|
||||
STOP_PROCESSING(SSobj, src)
|
||||
return
|
||||
|
||||
START_PROCESSING(SSobj, src)
|
||||
RegisterSignal(victim, COMSIG_MOVABLE_MOVED, PROC_REF(victim_gone))
|
||||
victim.overlay_fullscreen("flock_convert", /atom/movable/screen/fullscreen/flock_convert)
|
||||
|
||||
/obj/structure/flock/cage/proc/victim_gone(datum/source)
|
||||
SIGNAL_HANDLER
|
||||
if(!QDELING(src))
|
||||
deconstruct(FALSE)
|
||||
|
||||
/obj/structure/flock/cage/proc/target_gone(datum/source)
|
||||
SIGNAL_HANDLER
|
||||
set_eating_target(null)
|
||||
|
||||
/obj/structure/flock/cage/proc/ghost_check(mob/user)
|
||||
victim.throw_alert("ghost_cage", /atom/movable/screen/alert/ghost/flock)
|
||||
to_chat(victim, SPAN_GHOSTALERT("You may now click on the ghost prompt on your screen to leave your body. You will be alerted when you're removed from the cage."))
|
||||
if(tgui_alert(victim, "You may now ghost and keep respawnability. You will be notified if you leave the cage, would you like to do so?", "Ghosting", list("Yes", "No")) != "Yes")
|
||||
return
|
||||
victim.ghostize(GHOST_FLAGS_DEFAULT, ghost_color = "#19b299")
|
||||
|
||||
/mob/living/proc/test_cage()
|
||||
var/obj/structure/flock/cage/cage = new(get_turf(src))
|
||||
cage.cage_mob(src)
|
||||
@@ -0,0 +1,27 @@
|
||||
/obj/structure/chair/comfy/flock
|
||||
name = "buzzing alcove"
|
||||
desc = "It's a chair made of a strange material. Surprisingly soft to the touch, and extremely out of style."
|
||||
icon_state = "chair_flock"
|
||||
buildstacktype = /obj/item/stack/sheet/gnesis
|
||||
max_integrity = 50
|
||||
armor = list(MELEE = -20, BULLET = -20, LASER = 80, ENERGY = 80, BOMB = 0, RAD = 100, FIRE = 80, ACID = 100)
|
||||
|
||||
/obj/structure/chair/comfy/flock/Initialize(mapload, datum/flock/join_flock)
|
||||
. = ..()
|
||||
AddComponent(/datum/component/flock_protection, FALSE, TRUE, FALSE, FALSE)
|
||||
ADD_TRAIT(src, TRAIT_FLOCK_EXAMINE, INNATE_TRAIT)
|
||||
ADD_TRAIT(src, TRAIT_FLOCK_THING, INNATE_TRAIT)
|
||||
|
||||
/obj/structure/chair/comfy/flock/examine(mob/user)
|
||||
if(!isflockmob(user))
|
||||
return ..()
|
||||
|
||||
. = list(
|
||||
SPAN_FLOCKSAY("<b>###=- Ident confirmed, data packet received.</b>"),
|
||||
SPAN_FLOCKSAY("<b>ID:</b> Resting Chamber"),
|
||||
SPAN_FLOCKSAY("<b>System Integrity:</b> [get_integrity_percentage()]%"),
|
||||
SPAN_FLOCKSAY("<b>###=-</b>")
|
||||
)
|
||||
|
||||
/obj/structure/chair/comfy/flock/try_flock_convert(datum/flock/flock, force)
|
||||
return
|
||||
@@ -0,0 +1,68 @@
|
||||
/obj/structure/closet/flock
|
||||
name = "pulsing cache"
|
||||
desc = "A large closet-like structure that appears to lack a handle. The doors seem to open to the touch."
|
||||
icon_state = "flock"
|
||||
max_integrity = 100
|
||||
armor = list(MELEE = -20, BULLET = -20, LASER = 80, ENERGY = 80, BOMB = 0, RAD = 100, FIRE = 80, ACID = 100)
|
||||
pass_flags = PASSFLOCK
|
||||
material_drop = /obj/item/stack/sheet/gnesis
|
||||
enable_door_overlay = FALSE
|
||||
|
||||
/obj/structure/closet/flock/Initialize(mapload, datum/flock/join_flock)
|
||||
. = ..()
|
||||
var/static/list/loc_connections = list(
|
||||
COMSIG_ATOM_ENTERED = PROC_REF(on_crossed),
|
||||
)
|
||||
AddElement(/datum/element/connect_loc, loc_connections)
|
||||
AddComponent(/datum/component/flock_protection, FALSE, TRUE, FALSE, FALSE)
|
||||
ADD_TRAIT(src, TRAIT_FLOCK_EXAMINE, INNATE_TRAIT)
|
||||
ADD_TRAIT(src, TRAIT_FLOCK_THING, INNATE_TRAIT)
|
||||
|
||||
/obj/structure/closet/flock/proc/on_crossed(atom/source, atom/movable/crosser)
|
||||
SIGNAL_HANDLER
|
||||
|
||||
if(!isflockdrone(crosser) || broken)
|
||||
return
|
||||
|
||||
if(!HAS_TRAIT(crosser, TRAIT_FLOCKPHASE))
|
||||
animate_flockpass(crosser)
|
||||
|
||||
/obj/structure/closet/flock/CanPass(atom/movable/mover, border_dir)
|
||||
. = ..()
|
||||
if(.)
|
||||
return .
|
||||
|
||||
if(!isflockdrone(mover))
|
||||
return .
|
||||
|
||||
var/mob/living/basic/flock/drone/bird = mover
|
||||
if(HAS_TRAIT(bird, TRAIT_FLOCKPHASE))
|
||||
return TRUE
|
||||
|
||||
if(bird.can_flockphase())
|
||||
return TRUE
|
||||
|
||||
/obj/structure/closet/flock/CanPathfindPass(to_dir, datum/can_pass_info/pass_info)
|
||||
var/atom/M = locateUID(pass_info.caller_uid)
|
||||
if(!M)
|
||||
return ..()
|
||||
if(!isflockdrone(M))
|
||||
return ..()
|
||||
var/mob/living/basic/flock/drone/bird = M
|
||||
if(!bird.can_flockphase())
|
||||
return ..()
|
||||
return TRUE
|
||||
|
||||
/obj/structure/closet/flock/examine(mob/user)
|
||||
if(!isflockmob(user))
|
||||
return ..()
|
||||
|
||||
. = list(
|
||||
SPAN_FLOCKSAY("<b>###=- Ident confirmed, data packet received.</b>"),
|
||||
SPAN_FLOCKSAY("<b>ID:</b> Storage Capsule"),
|
||||
SPAN_FLOCKSAY("<b>System Integrity:</b> [get_integrity_percentage()]%"),
|
||||
SPAN_FLOCKSAY("<b>###=-</b>")
|
||||
)
|
||||
|
||||
/obj/structure/closet/flock/try_flock_convert(datum/flock/flock, force)
|
||||
return
|
||||
@@ -0,0 +1,166 @@
|
||||
/obj/structure/flock/collector
|
||||
name = "pulsing node"
|
||||
desc = "Seems to be pulsing."
|
||||
icon_state = "collector"
|
||||
|
||||
flock_desc = "Provides compute power and charges a nearby APC based on the number of Flock floor tiles it is connected to."
|
||||
flock_id = "Collector"
|
||||
|
||||
max_integrity = 60
|
||||
resource_cost = 200
|
||||
|
||||
/// The collection range.
|
||||
var/max_range = 4
|
||||
|
||||
/// How much power each flocktile grants, percentage of a cell's max charge.
|
||||
var/power_per_tile = 5
|
||||
|
||||
/// All flockturfs we're connected to.
|
||||
var/tmp/list/turf/simulated/floor/flock/connected_flockturfs = list()
|
||||
/// All turfs nearby we're tracking for state changes.
|
||||
var/tmp/list/turf/tracked_turfs = list()
|
||||
|
||||
/// Set to TRUE when the connected_turfs list needs an update. Prevents update_connections() being called 20 times during explosions.
|
||||
var/tmp/need_turfs_update = FALSE
|
||||
|
||||
var/cycle_interval = 20 SECONDS
|
||||
var/tmp/charge_per_cycle = 0
|
||||
COOLDOWN_DECLARE(charge_cd)
|
||||
|
||||
/obj/structure/flock/collector/Initialize(mapload, datum/flock/join_flock)
|
||||
. = ..()
|
||||
START_PROCESSING(SSobj, src)
|
||||
update_connections()
|
||||
info_tag.set_text("Bandwidth Provided: [bandwidth_provided]")
|
||||
AddComponent(/datum/component/flock_protection)
|
||||
|
||||
/obj/structure/flock/collector/Destroy()
|
||||
remove_flockturfs(connected_flockturfs)
|
||||
remove_tracked_turfs(tracked_turfs)
|
||||
return ..()
|
||||
|
||||
/obj/structure/flock/collector/update_icon_state()
|
||||
icon_state = length(connected_flockturfs) ? "collectoron" : "collector"
|
||||
return ..()
|
||||
|
||||
/obj/structure/flock/collector/Moved(atom/old_loc, movement_dir, forced, list/old_locs, momentum_change)
|
||||
. = ..()
|
||||
update_connections()
|
||||
|
||||
/obj/structure/flock/collector/flock_structure_examine(mob/user)
|
||||
var/obj/machinery/power/apc/apc = get_apc()
|
||||
|
||||
return list(
|
||||
SPAN_FLOCKSAY("<b>Connections:</b> Connected to [length(connected_flockturfs)] tile\s."),
|
||||
SPAN_FLOCKSAY("<b>Bandwidth Provided:</b> [bandwidth_provided]."),
|
||||
SPAN_FLOCKSAY("<b>APC Connected:</b> [(!apc?.cell || apc.cell.charge >= apc.cell.maxcharge) ? "Not charging." : "Charging local APC at [charge_per_cycle]% every [cycle_interval / 10] seconds."]")
|
||||
)
|
||||
|
||||
/obj/structure/flock/collector/process()
|
||||
if(need_turfs_update)
|
||||
update_connections()
|
||||
|
||||
var/new_bandwidth = length(connected_flockturfs) * power_per_tile
|
||||
charge_per_cycle = length(connected_flockturfs) / 4
|
||||
|
||||
if(new_bandwidth != bandwidth_provided)
|
||||
flock.remove_bandwidth_influence(bandwidth_provided)
|
||||
bandwidth_provided = new_bandwidth
|
||||
flock.add_bandwidth_influence(bandwidth_provided)
|
||||
info_tag.set_text("Bandwidth Provided: [bandwidth_provided]")
|
||||
|
||||
if(!COOLDOWN_FINISHED(src, charge_cd))
|
||||
return
|
||||
|
||||
COOLDOWN_START(src, charge_cd, cycle_interval)
|
||||
|
||||
var/obj/machinery/power/apc/apc = get_apc()
|
||||
if(isnull(apc))
|
||||
return
|
||||
|
||||
|
||||
apc.cell?.give(charge_per_cycle / 100 * apc.cell.maxcharge)
|
||||
apc.AddComponent(/datum/component/flock_ping/apc_power)
|
||||
|
||||
/obj/structure/flock/collector/proc/get_apc() as /obj/machinery/power/apc
|
||||
var/area/A = get_area(src)
|
||||
var/obj/machinery/power/apc/apc
|
||||
if(A.apc)
|
||||
apc = A.apc[0]
|
||||
return apc
|
||||
|
||||
/// Recalculate the turf connections and tracking.
|
||||
/obj/structure/flock/collector/proc/update_connections()
|
||||
var/list/old_tracked_turfs = tracked_turfs
|
||||
var/list/old_flockturfs = connected_flockturfs
|
||||
|
||||
var/turf/our_turf = get_turf(src)
|
||||
|
||||
var/list/considered_allturfs = list(our_turf)
|
||||
var/list/considered_flockturfs = list()
|
||||
|
||||
if(istype(our_turf, /turf/simulated/floor/flock))
|
||||
considered_flockturfs += our_turf
|
||||
|
||||
for(var/direction in GLOB.cardinal)
|
||||
var/turf/simulated/floor/flock/iter_turf = our_turf
|
||||
var/keep_flockturfs = isflockturf(our_turf)
|
||||
for(var/i in 1 to max_range)
|
||||
iter_turf = get_step(iter_turf, direction)
|
||||
|
||||
if(keep_flockturfs && (!istype(iter_turf, /turf/simulated/floor/flock) || iter_turf.broken))
|
||||
keep_flockturfs = FALSE
|
||||
|
||||
if(keep_flockturfs)
|
||||
considered_flockturfs += iter_turf
|
||||
|
||||
considered_allturfs += iter_turf
|
||||
|
||||
var/list/new_tracked_turfs = considered_allturfs - old_tracked_turfs
|
||||
var/list/removed_tracked_turfs = old_tracked_turfs - considered_allturfs
|
||||
remove_tracked_turfs(removed_tracked_turfs)
|
||||
add_tracked_turfs(new_tracked_turfs)
|
||||
|
||||
var/list/new_flockturfs = considered_flockturfs - old_flockturfs
|
||||
var/list/removed_flockturfs = old_flockturfs - considered_flockturfs
|
||||
remove_flockturfs(removed_flockturfs)
|
||||
add_flockturfs(new_flockturfs)
|
||||
|
||||
need_turfs_update = FALSE
|
||||
update_appearance(UPDATE_ICON_STATE)
|
||||
|
||||
/// Adds turfs to the flockturfs list.
|
||||
/obj/structure/flock/collector/proc/add_flockturfs(list/add)
|
||||
for(var/turf/simulated/floor/flock/flockturf as anything in add)
|
||||
LAZYADD(flockturf.connected_pylons, src)
|
||||
flockturf.update_power()
|
||||
|
||||
connected_flockturfs += add
|
||||
|
||||
/// Remove turfs from the flockturfs list.
|
||||
/obj/structure/flock/collector/proc/remove_flockturfs(list/remove)
|
||||
for(var/turf/simulated/floor/flock/flockturf as anything in remove)
|
||||
LAZYREMOVE(flockturf.connected_pylons, src)
|
||||
flockturf.update_power()
|
||||
|
||||
tracked_turfs -= remove
|
||||
|
||||
/// Add turfs to the tracked list.
|
||||
/obj/structure/flock/collector/proc/add_tracked_turfs(list/add)
|
||||
for(var/turf/T as anything in add)
|
||||
RegisterSignal(T, COMSIG_TURF_CHANGE, PROC_REF(on_tracked_turf_change))
|
||||
|
||||
tracked_turfs += add
|
||||
|
||||
/// Remove turfs from the tracked list.
|
||||
/obj/structure/flock/collector/proc/remove_tracked_turfs(list/remove)
|
||||
for(var/turf/T as anything in remove)
|
||||
UnregisterSignal(T, COMSIG_TURF_CHANGE)
|
||||
|
||||
tracked_turfs -= remove
|
||||
|
||||
/// Called when any of the connected turfs is changed.
|
||||
/obj/structure/flock/collector/proc/on_tracked_turf_change(datum/source, path, list/new_baseturfs, flags, list/post_change_callbacks)
|
||||
SIGNAL_HANDLER
|
||||
|
||||
need_turfs_update = TRUE
|
||||
@@ -0,0 +1,37 @@
|
||||
/obj/structure/flock/compute
|
||||
name = "strange computer"
|
||||
desc = "It almost looks like a terminal of some kind."
|
||||
|
||||
flock_desc = "A computing node that provides bandwidth to the Flock."
|
||||
flock_id = "Compute Node"
|
||||
|
||||
max_integrity = 60
|
||||
icon_state = "compute"
|
||||
bandwidth_provided = 30
|
||||
|
||||
/obj/structure/flock/compute/Initialize(mapload, datum/flock/join_flock)
|
||||
. = ..()
|
||||
light_range = 1
|
||||
light_color = "#7BFFFFa2"
|
||||
light_power = 0.3
|
||||
|
||||
update_appearance()
|
||||
|
||||
/obj/structure/flock/compute/update_overlays()
|
||||
. = ..()
|
||||
overlays.Cut()
|
||||
var/image/screen = image(icon, "compute_screen")
|
||||
screen.pixel_y = 16
|
||||
. += screen
|
||||
var/image/display = image(icon, "compute_display[rand(1, 9)]")
|
||||
display.pixel_y = 19
|
||||
. += display
|
||||
|
||||
/obj/structure/flock/compute/flock_structure_examine(mob/user)
|
||||
return list(
|
||||
SPAN_FLOCKSAY("<b>Bandwidth Provided:</b> [bandwidth_provided].")
|
||||
)
|
||||
|
||||
/obj/structure/flock/compute/update_info_tag()
|
||||
info_tag?.set_text("Bandwidth: [bandwidth_provided]")
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
/obj/machinery/door/flock
|
||||
icon = 'icons/goonstation/mob/featherzone.dmi'
|
||||
name = "imposing wall"
|
||||
desc = "It sounds like it's hollow."
|
||||
max_integrity = 200
|
||||
armor = list(MELEE = -20, BULLET = -20, LASER = 80, ENERGY = 80, BOMB = 0, RAD = 100, FIRE = 80, ACID = 100)
|
||||
autoclose = TRUE
|
||||
auto_close_time = 5 SECONDS
|
||||
safe = FALSE
|
||||
|
||||
/obj/machinery/door/flock/Initialize(mapload)
|
||||
. = ..()
|
||||
ADD_TRAIT(src, TRAIT_FLOCK_EXAMINE, INNATE_TRAIT)
|
||||
ADD_TRAIT(src, TRAIT_FLOCK_THING, INNATE_TRAIT)
|
||||
AddComponent(/datum/component/flock_object)
|
||||
AddComponent(/datum/component/flock_protection, report_unarmed=FALSE)
|
||||
|
||||
/obj/machinery/door/flock/get_flock_id()
|
||||
return "Solid seal aperature"
|
||||
|
||||
/obj/machinery/door/flock/default_deconstruction_screwdriver(mob/user, icon_state_open, icon_state_closed, obj/item/screwdriver)
|
||||
return FALSE
|
||||
|
||||
/obj/machinery/door/flock/allowed(mob/M)
|
||||
return isflockmob(M)
|
||||
|
||||
/obj/machinery/door/flock/CanPathfindPass(to_dir, datum/can_pass_info/pass_info)
|
||||
var/atom/M = locateUID(pass_info.caller_uid)
|
||||
if(!M)
|
||||
return ..()
|
||||
if(isflockmob(M))
|
||||
return TRUE
|
||||
return ..()
|
||||
|
||||
/obj/machinery/door/flock/crush()
|
||||
for(var/turf/T in get_airlock_turfs())
|
||||
for(var/mob/living/L in T)
|
||||
if(isflockmob(L))
|
||||
continue
|
||||
L.visible_message(SPAN_WARNING("[src] closes on [L], crushing [L.p_them()]!"), SPAN_USERDANGER("[src] closes on you and crushes you!"))
|
||||
if(isalien(L)) // For xenos
|
||||
L.adjustBruteLoss(DOOR_CRUSH_DAMAGE * 1.5) // Xenos go into crit after aproximately the same amount of crushes as humans.
|
||||
L.emote("roar")
|
||||
else if(ishuman(L)) // For humans
|
||||
L.adjustBruteLoss(DOOR_CRUSH_DAMAGE)
|
||||
if(L.stat == CONSCIOUS)
|
||||
L.emote("scream")
|
||||
L.Weaken(10 SECONDS)
|
||||
else // for simple_animals & borgs
|
||||
L.adjustBruteLoss(DOOR_CRUSH_DAMAGE)
|
||||
L.add_splatter_floor(T)
|
||||
for(var/obj/mecha/M in T)
|
||||
M.take_damage(DOOR_CRUSH_DAMAGE)
|
||||
|
||||
/obj/machinery/door/flock/deconstruct(disassembled)
|
||||
. = ..()
|
||||
var/turf/T = get_turf(src)
|
||||
if(disassembled)
|
||||
new /obj/item/stack/sheet/gnesis(T, 2)
|
||||
return
|
||||
new /obj/item/stack/sheet/gnesis(T, 1)
|
||||
|
||||
/obj/machinery/door/flock/do_animate(animation)
|
||||
. = ..()
|
||||
if(animation == "deny")
|
||||
playsound(src, 'sound/goonstation/flockmind/flockdrone_door_deny.ogg', 50, TRUE, -2)
|
||||
|
||||
/obj/machinery/door/flock/try_flock_convert(datum/flock/flock, force)
|
||||
return
|
||||
|
||||
/obj/machinery/door/flock/examine(mob/user)
|
||||
if(!isflockmob(user))
|
||||
return ..()
|
||||
|
||||
. = list(
|
||||
SPAN_FLOCKSAY("<b>###=- Ident confirmed, data packet received.</b>"),
|
||||
SPAN_FLOCKSAY("<b>ID:</b> [get_flock_id()]"),
|
||||
SPAN_FLOCKSAY("<b>System Integrity:</b> [get_integrity_percentage()]%"),
|
||||
SPAN_FLOCKSAY("<b>###=-</b>"),
|
||||
)
|
||||
|
||||
if(stat & BROKEN)
|
||||
. += SPAN_FLOCKSAY("<b>FUNCTION CRITICALLY IMPAIRED, REPAIRS REQUIRED</>")
|
||||
. += SPAN_FLOCKSAY("<b>###=-</b>")
|
||||
@@ -0,0 +1,35 @@
|
||||
/obj/structure/flock/egg
|
||||
icon_state = "egg"
|
||||
name = "glowing orb"
|
||||
desc = "Some sort of small machine. It looks like its getting ready for something."
|
||||
flock_desc = "Will soon hatch into a Flockdrone."
|
||||
|
||||
anchored = FALSE
|
||||
density = FALSE
|
||||
|
||||
max_integrity = 30
|
||||
|
||||
flock_id = "Second-Stage Assembler"
|
||||
build_time = 6 SECONDS
|
||||
no_flock_decon = TRUE
|
||||
|
||||
/obj/structure/flock/egg/finish_building()
|
||||
. = ..()
|
||||
|
||||
AddComponent(/datum/component/flock_protection)
|
||||
spawn_mobs()
|
||||
qdel(src)
|
||||
|
||||
/obj/structure/flock/egg/update_info_tag()
|
||||
info_tag.set_text("Hatch Time: [build_time_left()] seconds")
|
||||
|
||||
/obj/structure/flock/egg/proc/spawn_mobs()
|
||||
new /mob/living/basic/flock/drone(get_turf(src), flock)
|
||||
|
||||
/obj/structure/flock/egg/bit
|
||||
flock_id = "Secondary Second-Stage Assembler"
|
||||
flock_desc = "Will soon hatch into Flockbits."
|
||||
|
||||
/obj/structure/flock/egg/bit/spawn_mobs()
|
||||
for(var/i in 1 to 3)
|
||||
new /mob/living/basic/flock/bit(get_turf(src), flock)
|
||||
@@ -0,0 +1,92 @@
|
||||
/obj/structure/flock/fabricator
|
||||
name = "angled pedestal"
|
||||
desc = "A strange machine."
|
||||
|
||||
flock_id = "Fabricator"
|
||||
flock_desc = "A converter that turns its contents into substrate cubes."
|
||||
icon_state = "reclaimer"
|
||||
|
||||
max_integrity = 20
|
||||
|
||||
var/production_interval = 10 SECONDS
|
||||
var/substrate_per_interval = 50
|
||||
|
||||
var/tmp/datum/point_holder/substrate_remaining
|
||||
var/tmp/timer_id
|
||||
|
||||
/obj/structure/flock/fabricator/Initialize(mapload, datum/flock/join_flock)
|
||||
. = ..()
|
||||
substrate_remaining = new()
|
||||
timer_id = addtimer(CALLBACK(src, PROC_REF(produce)), production_interval, TIMER_STOPPABLE | TIMER_LOOP | TIMER_DELETE_ME)
|
||||
|
||||
/obj/structure/flock/fabricator/Destroy()
|
||||
QDEL_NULL(substrate_remaining)
|
||||
deltimer(timer_id)
|
||||
return ..()
|
||||
|
||||
/obj/structure/flock/fabricator/update_icon_state()
|
||||
if(substrate_remaining.has_points())
|
||||
icon_state = "reclaimer"
|
||||
else
|
||||
icon_state = "reclaimer-off"
|
||||
. = ..()
|
||||
|
||||
/obj/structure/flock/fabricator/flock_structure_examine(mob/user)
|
||||
. = ..()
|
||||
. += SPAN_FLOCKSAY("<b>Substrate remaining:</b> [substrate_remaining.has_points()].")
|
||||
|
||||
/obj/structure/flock/fabricator/update_info_tag()
|
||||
info_tag.set_text("Substrate remaining: [substrate_remaining.has_points()]")
|
||||
|
||||
/// Called every 10 seconds via looping timer until there's no substrate left.
|
||||
/obj/structure/flock/fabricator/proc/produce()
|
||||
if(substrate_remaining.has_points())
|
||||
var/obj/item/flock_cube/cube = new(drop_location())
|
||||
cube.substrate = min(substrate_remaining.has_points(), substrate_per_interval)
|
||||
|
||||
substrate_remaining.remove_points(cube.substrate)
|
||||
|
||||
if(!substrate_remaining.has_points())
|
||||
stop_producing()
|
||||
|
||||
update_info_tag()
|
||||
|
||||
/obj/structure/flock/fabricator/proc/stop_producing()
|
||||
deltimer(timer_id)
|
||||
timer_id = null
|
||||
if(QDELING(src))
|
||||
return
|
||||
|
||||
flock_talk(src, "ALERT: No substrate remaining.", flock, involuntary = TRUE)
|
||||
update_appearance(UPDATE_ICON_STATE)
|
||||
|
||||
//
|
||||
// All of the try_flock_convert shit
|
||||
//
|
||||
/obj/machinery/economy/vending/try_flock_convert(datum/flock/flock, force)
|
||||
var/substrate
|
||||
for(var/datum/data/vending_product/product as anything in product_records)
|
||||
substrate += 3 * product.get_amount_left()
|
||||
|
||||
if(!substrate)
|
||||
qdel(src)
|
||||
return
|
||||
|
||||
var/obj/structure/flock/fabricator/fab = new(get_turf(src), flock)
|
||||
fab.substrate_remaining.add_points(substrate)
|
||||
fab.update_info_tag()
|
||||
update_appearance(UPDATE_ICON_STATE)
|
||||
qdel(src)
|
||||
|
||||
/obj/structure/reagent_dispensers/try_flock_convert(datum/flock/flock, force)
|
||||
var/substrate = reagents.total_volume / 10 // 100 substrate for a full tank of 1000u
|
||||
|
||||
if(!substrate)
|
||||
qdel(src)
|
||||
return
|
||||
|
||||
var/obj/structure/flock/fabricator/fab = new(get_turf(src), flock)
|
||||
fab.substrate_remaining.add_points(substrate)
|
||||
fab.update_info_tag()
|
||||
update_appearance(UPDATE_ICON_STATE)
|
||||
qdel(src)
|
||||
@@ -0,0 +1,155 @@
|
||||
/obj/structure/grille/flock
|
||||
desc = "A robust framework of gnesis tubules."
|
||||
icon = 'icons/goonstation/mob/featherzone.dmi'
|
||||
icon_state = "barricade-0"
|
||||
armor = list(MELEE = -20, BULLET = -20, LASER = 80, ENERGY = 80, BOMB = 0, RAD = 100, FIRE = 80, ACID = 100)
|
||||
pass_flags = PASSFLOCK
|
||||
flags = CONDUCT | NODECONSTRUCT
|
||||
rods_type = null
|
||||
rods_amount = 0
|
||||
rods_broken = 0
|
||||
broken_type = /obj/structure/grille/flock/broken
|
||||
|
||||
/obj/structure/grille/flock/Initialize(mapload)
|
||||
. = ..()
|
||||
|
||||
var/static/list/loc_connections = list(
|
||||
COMSIG_ATOM_ENTERED = PROC_REF(on_crossed),
|
||||
)
|
||||
AddElement(/datum/element/connect_loc, loc_connections)
|
||||
AddComponent(/datum/component/flock_protection, FALSE, TRUE, FALSE, FALSE)
|
||||
ADD_TRAIT(src, TRAIT_FLOCK_EXAMINE, INNATE_TRAIT)
|
||||
ADD_TRAIT(src, TRAIT_FLOCK_THING, INNATE_TRAIT)
|
||||
|
||||
/obj/structure/grille/flock/examine(mob/user)
|
||||
if(!isflockmob(user))
|
||||
return ..()
|
||||
|
||||
. = list(
|
||||
SPAN_FLOCKSAY("<b>###=- Ident confirmed, data packet received.</b>"),
|
||||
SPAN_FLOCKSAY("<b>ID:</b> Barricade"),
|
||||
SPAN_FLOCKSAY("<b>System Integrity:</b> [get_integrity_percentage()]%"),
|
||||
SPAN_FLOCKSAY("<b>###=-</b>")
|
||||
)
|
||||
|
||||
/obj/structure/grille/flock/take_damage(damage_amount, damage_type = BRUTE, damage_flag = 0, sound_effect = 1, attack_dir)
|
||||
. = ..()
|
||||
update_icon(UPDATE_ICON_STATE)
|
||||
|
||||
/obj/structure/grille/flock/update_icon_state()
|
||||
if(QDELETED(src) || broken)
|
||||
return
|
||||
|
||||
var/ratio = obj_integrity / max_integrity
|
||||
|
||||
if(ratio > 0.5)
|
||||
return
|
||||
icon_state = "barricade-[rand(1,3)]"
|
||||
|
||||
/obj/structure/grille/flock/Bumped(atom/user)
|
||||
if(isliving(user))
|
||||
if(!(shockcooldown <= world.time))
|
||||
return
|
||||
if(!anchored || broken) // unanchored/broken grilles are never connected
|
||||
return
|
||||
if(!prob(70))
|
||||
return
|
||||
if(!in_range(src, user))// To prevent TK and mech users from getting shocked
|
||||
return
|
||||
var/mob/living/M = user
|
||||
var/siemens = 1
|
||||
if(ishuman(M))
|
||||
var/mob/living/carbon/human/H = M
|
||||
if(H.gloves)
|
||||
var/obj/item/clothing/gloves/G = H.gloves
|
||||
siemens = G.siemens_coefficient
|
||||
M.electrocute_act(30, src, siemens)
|
||||
shockcooldown = world.time + my_shockcooldown
|
||||
|
||||
/obj/structure/grille/flock/temperature_expose(exposed_temperature, exposed_volume)
|
||||
if(!broken)
|
||||
if(exposed_temperature > T0C + 32000)
|
||||
take_damage(1, BURN, 0, 0)
|
||||
|
||||
/obj/structure/grille/flock/shock(mob/user, prb)
|
||||
if(!anchored || broken) // unanchored/broken grilles are never connected
|
||||
return FALSE
|
||||
if(!prob(prb))
|
||||
return FALSE
|
||||
if(!in_range(src, user))// To prevent TK and mech users from getting shocked
|
||||
return FALSE
|
||||
if(isflockmob(user))
|
||||
return FALSE
|
||||
var/mob/living/M = user
|
||||
var/siemens = 1
|
||||
if(ishuman(M))
|
||||
var/mob/living/carbon/human/H = M
|
||||
if(H.gloves)
|
||||
var/obj/item/clothing/gloves/G = H.gloves
|
||||
siemens = G.siemens_coefficient
|
||||
M.electrocute_act(20, src, siemens)
|
||||
do_sparks(3, 1, src)
|
||||
return TRUE
|
||||
|
||||
/obj/structure/grille/flock/hitby(atom/movable/AM, skipcatch, hitpush, blocked, datum/thrownthing/throwingdatum)
|
||||
if(isobj(AM))
|
||||
if(prob(50) && anchored && !broken)
|
||||
var/obj/O = AM
|
||||
if(O.throwforce != 0)// don't want to let people spam tesla bolts, this way it will break after time
|
||||
playsound(src, 'sound/magic/lightningshock.ogg', 100, TRUE, extrarange = 5)
|
||||
tesla_zap(src, 3, 12000, ZAP_MOB_DAMAGE | ZAP_OBJ_DAMAGE | ZAP_MOB_STUN | ZAP_ALLOW_DUPLICATES)
|
||||
return ..()
|
||||
|
||||
/obj/structure/grille/flock/obj_break()
|
||||
if(!broken)
|
||||
new broken_type(loc)
|
||||
qdel(src)
|
||||
|
||||
/obj/structure/grille/flock/proc/on_crossed(atom/source, atom/movable/crosser)
|
||||
SIGNAL_HANDLER
|
||||
|
||||
if(!isflockdrone(crosser) || broken)
|
||||
return
|
||||
|
||||
if(!HAS_TRAIT(crosser, TRAIT_FLOCKPHASE))
|
||||
animate_flockpass(crosser)
|
||||
|
||||
/obj/structure/grille/flock/CanPass(atom/movable/mover, border_dir)
|
||||
. = ..()
|
||||
if(.)
|
||||
return .
|
||||
|
||||
if(!isflockdrone(mover))
|
||||
return .
|
||||
|
||||
var/mob/living/basic/flock/drone/bird = mover
|
||||
if(HAS_TRAIT(bird, TRAIT_FLOCKPHASE))
|
||||
return TRUE
|
||||
|
||||
if(bird.can_flockphase())
|
||||
return TRUE
|
||||
|
||||
/obj/structure/grille/flock/CanPathfindPass(to_dir, datum/can_pass_info/pass_info)
|
||||
var/atom/M = locateUID(pass_info.caller_uid)
|
||||
if(!M)
|
||||
return ..()
|
||||
if(!isflockdrone(M))
|
||||
return ..()
|
||||
var/mob/living/basic/flock/drone/bird = M
|
||||
if(!bird.can_flockphase())
|
||||
return ..()
|
||||
return TRUE
|
||||
|
||||
/obj/structure/grille/flock/broken
|
||||
desc = "A broken framework of gnesis tubules."
|
||||
icon_state = "barricade-cut"
|
||||
armor = list(MELEE = -20, BULLET = -20, LASER = 80, ENERGY = 80, BOMB = 0, RAD = 100, FIRE = 80, ACID = 100)
|
||||
density = FALSE
|
||||
obj_integrity = 20
|
||||
broken = TRUE
|
||||
grille_type = /obj/structure/grille/flock
|
||||
broken_type = null
|
||||
|
||||
|
||||
/obj/structure/grille/flock/try_flock_convert(datum/flock/flock, force)
|
||||
return
|
||||
@@ -0,0 +1,100 @@
|
||||
/obj/structure/flock/interceptor
|
||||
name = "glowing fountain"
|
||||
desc = "Some kind of fountain. The fluid inside ripples with energy."
|
||||
icon_state = "interceptor-off"
|
||||
|
||||
flock_desc = "A defense turret that fires high speed gnesis bolts at nearby projectiles, annihilating them."
|
||||
flock_id = "Interceptor"
|
||||
|
||||
resource_cost = 100
|
||||
|
||||
var/tmp/datum/proximity_monitor/advanced/interceptor/proxmon
|
||||
|
||||
/// How long it takes to recharge
|
||||
var/recharge_time = 10 SECONDS
|
||||
/// Is it charged?
|
||||
var/tmp/is_charged = FALSE
|
||||
/// world.time the interceptor will be charged (might be off by 1 sometimes? idk timers wierd)
|
||||
var/tmp/ready_time
|
||||
/// timer id for recharging
|
||||
var/tmp/recharge_timer_id
|
||||
|
||||
/obj/structure/flock/interceptor/Initialize(mapload, datum/flock/join_flock)
|
||||
. = ..()
|
||||
proxmon = new(src, 3, TRUE)
|
||||
process()
|
||||
AddComponent(/datum/component/flock_protection)
|
||||
|
||||
/obj/structure/flock/interceptor/Destroy()
|
||||
QDEL_NULL(proxmon)
|
||||
deltimer(recharge_timer_id)
|
||||
return ..()
|
||||
|
||||
/obj/structure/flock/interceptor/update_icon_state()
|
||||
if(!active)
|
||||
icon_state = "interceptor-off"
|
||||
else if(is_charged)
|
||||
icon_state = "interceptor-ready"
|
||||
else
|
||||
icon_state = "interceptor-generating"
|
||||
. = ..()
|
||||
|
||||
/obj/structure/flock/interceptor/update_info_tag()
|
||||
info_tag.set_text(get_status())
|
||||
|
||||
/obj/structure/flock/interceptor/flock_structure_examine(mob/user)
|
||||
. = ..()
|
||||
. += "[get_status()]."
|
||||
|
||||
/obj/structure/flock/interceptor/process()
|
||||
if(active)
|
||||
if(flock.available_bandwidth() < 0)
|
||||
set_active(FALSE)
|
||||
else
|
||||
if(flock.can_afford(active_bandwidth_cost))
|
||||
set_active(TRUE)
|
||||
|
||||
/obj/structure/flock/interceptor/set_active(new_state)
|
||||
. = ..()
|
||||
if(isnull(.))
|
||||
return
|
||||
|
||||
if(. == TRUE)
|
||||
begin_charging()
|
||||
else
|
||||
is_charged = FALSE
|
||||
deltimer(recharge_timer_id)
|
||||
|
||||
/obj/structure/flock/interceptor/proc/begin_charging()
|
||||
is_charged = FALSE
|
||||
recharge_timer_id = addtimer(CALLBACK(src, PROC_REF(ready_to_fire)), recharge_time, TIMER_DELETE_ME | TIMER_STOPPABLE)
|
||||
update_appearance()
|
||||
|
||||
/// Called when recharge_cd is ready to fire
|
||||
/obj/structure/flock/interceptor/proc/ready_to_fire()
|
||||
is_charged = TRUE
|
||||
update_appearance()
|
||||
|
||||
/obj/structure/flock/interceptor/proc/get_status()
|
||||
if(!active)
|
||||
return "Idle"
|
||||
|
||||
if(is_charged)
|
||||
return "Ready"
|
||||
|
||||
return "Recharging."
|
||||
|
||||
/// Called when a projectile enters the view of the interceptor.
|
||||
/obj/structure/flock/interceptor/proc/try_intercept_projectile(obj/projectile/P)
|
||||
if(!is_charged)
|
||||
return FALSE
|
||||
|
||||
if(!istype(P, /obj/projectile/bullet))
|
||||
return FALSE
|
||||
|
||||
playsound(src, 'sound/goonstation/flockmind/flockdrone_fart.ogg', 50, TRUE) // It honestly kind of fits??
|
||||
Beam(get_turf(P), icon_state = "rped_upgrade", icon = 'icons/effects/effects.dmi', time = 0.5 SECONDS)
|
||||
qdel(P)
|
||||
|
||||
begin_charging()
|
||||
return TRUE
|
||||
@@ -0,0 +1,28 @@
|
||||
/obj/structure/lattice/flock
|
||||
name = "fibrenet"
|
||||
desc = "A floating lattice of lightweight gnesis."
|
||||
icon = 'icons/goonstation/mob/featherzone.dmi'
|
||||
icon_state = "fibrenet"
|
||||
base_icon_state = "fibrenet"
|
||||
armor = list(MELEE = 20, BULLET = 10, LASER = 10, ENERGY = 0, BOMB = 10, RAD = 0, FIRE = 70, ACID = 60)
|
||||
max_integrity = 30
|
||||
|
||||
/obj/structure/lattice/flock/Initialize(mapload)
|
||||
. = ..()
|
||||
AddComponent(/datum/component/flock_protection, FALSE, TRUE, FALSE, FALSE)
|
||||
ADD_TRAIT(src, TRAIT_FLOCK_EXAMINE, INNATE_TRAIT)
|
||||
ADD_TRAIT(src, TRAIT_FLOCK_THING, INNATE_TRAIT)
|
||||
|
||||
/obj/structure/lattice/flock/examine(mob/user)
|
||||
if(!isflockmob(user))
|
||||
return ..()
|
||||
|
||||
. = list(
|
||||
SPAN_FLOCKSAY("<b>###=- Ident confirmed, data packet received.</b>"),
|
||||
SPAN_FLOCKSAY("<b>ID:</b> Structural Foundation"),
|
||||
SPAN_FLOCKSAY("<b>System Integrity:</b> [get_integrity_percentage()]%"),
|
||||
SPAN_FLOCKSAY("<b>###=-</b>")
|
||||
)
|
||||
|
||||
/obj/structure/lattice/flock/try_flock_convert(datum/flock/flock, force)
|
||||
return
|
||||
@@ -0,0 +1,35 @@
|
||||
/obj/machinery/light/floor/has_bulb/flock
|
||||
icon = 'icons/goonstation/objects/lighting.dmi'
|
||||
icon_state = "flock_floor"
|
||||
color = "#72bfac"
|
||||
light_color = "#72bfac"
|
||||
brightness_color = "#72bfac"
|
||||
base_state = "flock_floor"
|
||||
glow_icon = 'icons/goonstation/objects/lighting.dmi'
|
||||
glow_icon_state = "flock_floor"
|
||||
nightshift_allowed = FALSE
|
||||
no_emergency = TRUE
|
||||
|
||||
/obj/machinery/light/floor/has_bulb/flock/try_flock_convert(datum/flock/flock, force)
|
||||
return
|
||||
|
||||
/obj/machinery/light/floor/has_bulb/get_flock_id()
|
||||
return "Light emitter"
|
||||
|
||||
/obj/machinery/light/flock
|
||||
icon = 'icons/goonstation/objects/lighting.dmi'
|
||||
icon_state = "flock"
|
||||
color = "#72bfac"
|
||||
light_color = "#72bfac"
|
||||
brightness_color = "#72bfac"
|
||||
base_state = "flock"
|
||||
glow_icon = 'icons/goonstation/objects/lighting.dmi'
|
||||
glow_icon_state = "flock"
|
||||
nightshift_allowed = FALSE
|
||||
no_emergency = TRUE
|
||||
|
||||
/obj/machinery/light/flock/try_flock_convert(datum/flock/flock, force)
|
||||
return
|
||||
|
||||
/obj/machinery/light/flock/get_flock_id()
|
||||
return "Light emitter"
|
||||
@@ -0,0 +1,228 @@
|
||||
/obj/structure/flock/relay
|
||||
icon = 'icons/goonstation/objects/featherzone-160x160.dmi'
|
||||
icon_state = "structure-relay"
|
||||
|
||||
name = "titanic polyhedron"
|
||||
desc = "The sight of the towering geodesic sphere fills you with dread. A thousand voices whisper to you."
|
||||
|
||||
pixel_x = -64
|
||||
pixel_y = -64
|
||||
|
||||
flock_desc = "Your goal and purpose. Defend it until it can broadcast the Signal."
|
||||
flock_id = "Signal Relay Broadcast Amplifier"
|
||||
|
||||
max_integrity = 500
|
||||
move_resist = MOVE_FORCE_OVERPOWERING
|
||||
|
||||
resource_cost = 750
|
||||
|
||||
allow_flockpass = FALSE
|
||||
no_flock_decon = TRUE
|
||||
|
||||
/// The object showing admins/dchat the time left
|
||||
var/obj/effect/countdown/flock/counter
|
||||
|
||||
/// Background noise
|
||||
var/datum/looping_sound/flock_relay/soundloop
|
||||
|
||||
/// Time the structure started.
|
||||
var/tmp/started_time
|
||||
/// How long it takes until the signal is broadcast and the flock wins :D
|
||||
var/tmp/win_time = 360 SECONDS
|
||||
var/tmp/flock_won_da_game = FALSE
|
||||
|
||||
/// Radius for turf conversion. Automatically increments during processing.
|
||||
var/tmp/conversion_radius = 3
|
||||
|
||||
var/tmp/list/turfs_to_convert = list()
|
||||
|
||||
/obj/structure/flock/relay/Initialize(mapload, datum/flock/join_flock)
|
||||
. = ..()
|
||||
for(var/turf/simulated/T in range(3, src))
|
||||
T.ChangeTurf(/turf/simulated/floor/flock)
|
||||
flock.claim_turf(T)
|
||||
for(var/obj/structure/S in orange(3, src))
|
||||
if(!(S.flags & INDESTRUCTIBLE) && !istype(S, /obj/structure/flock) && !istype(S, /obj/machinery/atmospherics) && !istype(S, /obj/structure/cable))
|
||||
S.Destroy()
|
||||
for(var/obj/machinery/MA in orange(3, src))
|
||||
if(!(MA.flags & INDESTRUCTIBLE))
|
||||
MA.Destroy()
|
||||
for(var/obj/item/I in range(2, src))
|
||||
var/turf/T = get_edge_target_turf(src, get_dir(src, I))
|
||||
I.throw_at(T, 3, 1)
|
||||
for(var/mob/M in range(2, src))
|
||||
var/turf/T = get_edge_target_turf(src, get_dir(src, M))
|
||||
M.throw_at(T, 3, 1)
|
||||
|
||||
AddComponent(/datum/component/multitile, list(
|
||||
list(
|
||||
list(1, 1, 1, 1, 1),
|
||||
list(1, 1, 1, 1, 1),
|
||||
list(1, 1, MACH_CENTER, 1, 1),
|
||||
list(1, 1, 1, 1, 1),
|
||||
list(1, 1, 1, 1, 1)
|
||||
)
|
||||
))
|
||||
AddComponent(/datum/component/flock_protection)
|
||||
soundloop = new(get_turf(src), start_immediately = TRUE)
|
||||
started_time = world.time
|
||||
flock.built_relay = src
|
||||
flock.set_flock_game_status(FLOCK_ENDGAME_RELAY_BUILT)
|
||||
|
||||
log_game("The Flock ([flock?.name || "NULL"]) has constructed a relay at [AREACOORD(src)].")
|
||||
message_admins("The Flock has constructed the relay at [ADMIN_VERBOSEJMP(src)].")
|
||||
SSshuttle.registerHostileEnvironment(src, FALSE)
|
||||
|
||||
to_chat(
|
||||
flock.overmind,
|
||||
SPAN_FLOCKSAY(SPAN_BIG("You pool the collective processing power of The Flock to transmit The Signal. If the relay is destroyed, so too will be The Flock!"))
|
||||
)
|
||||
|
||||
flock_talk(null, "THE RELAY HAS BEEN CONSTRUCTED! DEFEND IT AT ALL COSTS! BRING FORTH THE FULL BREADTH OF THE DIVINE FLOCK!", flock)
|
||||
counter = new(src)
|
||||
counter.start()
|
||||
addtimer(CALLBACK(src, PROC_REF(announce_relay)), 10 SECONDS)
|
||||
|
||||
START_PROCESSING(SSprocessing, src)
|
||||
|
||||
/obj/structure/flock/relay/Destroy()
|
||||
SSshuttle.clearHostileEnvironment(src)
|
||||
STOP_PROCESSING(SSprocessing, src)
|
||||
|
||||
if(flock)
|
||||
flock.free_structure(src)
|
||||
flock.game_over(completely_destroy = TRUE)
|
||||
turfs_to_convert = null
|
||||
|
||||
qdel(soundloop)
|
||||
qdel(counter)
|
||||
return ..()
|
||||
|
||||
/obj/structure/flock/relay/take_damage(damage_amount, damage_type, damage_flag, sound_effect, attack_dir, armor_penetration, allow_break)
|
||||
. = ..()
|
||||
if(. >= 5)
|
||||
var/alpha_min = clamp(255 - . * 6, 100, 255)
|
||||
animate(src, time = 0.1 SECONDS, color = list(1.5,0,0,0, 0,1.5,0,0, 0,0,1.5,0, 0,0,0,alpha_min/255, 0,0,0,0))
|
||||
animate(time = 0.3 SECONDS, color = null, alpha = 255)
|
||||
|
||||
/obj/structure/flock/relay/examine(mob/user)
|
||||
. = ..()
|
||||
if(flock_won_da_game && !isflockmob(user))
|
||||
. += SPAN_FLOCKSAY("Your life flashes before your eyes.")
|
||||
|
||||
/obj/structure/flock/relay/update_overlays()
|
||||
. = ..()
|
||||
overlays.Cut()
|
||||
if(flock_won_da_game)
|
||||
. += "structure-relay-sparks"
|
||||
|
||||
/obj/structure/flock/relay/flock_structure_examine(mob/user)
|
||||
var/timeleft = (started_time + win_time - world.time) / 10
|
||||
if(timeleft)
|
||||
return list(
|
||||
SPAN_FLOCKSAY("<b>Broadcast In:</b> [(started_time + win_time - world.time) / 10] second\s.")
|
||||
)
|
||||
else
|
||||
return list(
|
||||
SPAN_FLOCKSAY("<b><i>BROADCASTING IN PROGRESS.</i></b>")
|
||||
)
|
||||
|
||||
/obj/structure/flock/relay/update_info_tag()
|
||||
if(!flock_won_da_game)
|
||||
info_tag?.set_text("Broadcast in: [(started_time + win_time - world.time) / 10] second\s")
|
||||
else
|
||||
info_tag?.set_text("Transmitting")
|
||||
|
||||
/obj/structure/flock/relay/process()
|
||||
if(world.time >= (started_time + win_time))
|
||||
lorimer_burst()
|
||||
return PROCESS_KILL
|
||||
|
||||
update_info_tag()
|
||||
|
||||
var/turf/base = get_turf(src)
|
||||
|
||||
/// Get a chebyshev ring without gigantic list ops.
|
||||
if(!length(turfs_to_convert))
|
||||
for(var/xo in -conversion_radius to conversion_radius)
|
||||
var/turf/potential_turf = locate(base.x + xo, base.y + conversion_radius, base.z)
|
||||
if(potential_turf)
|
||||
turfs_to_convert += potential_turf
|
||||
|
||||
potential_turf = locate(base.x - xo, base.y - conversion_radius, base.z)
|
||||
if(potential_turf)
|
||||
turfs_to_convert += potential_turf
|
||||
|
||||
for(var/yo in -conversion_radius to conversion_radius)
|
||||
var/turf/potential_turf = locate(base.x + conversion_radius, base.y + yo, base.z)
|
||||
if(potential_turf)
|
||||
turfs_to_convert += potential_turf
|
||||
|
||||
potential_turf = locate(base.x - conversion_radius, base.y - yo, base.z)
|
||||
if(potential_turf)
|
||||
turfs_to_convert += potential_turf
|
||||
|
||||
conversion_radius++
|
||||
|
||||
// Convert 5 turfs per second.
|
||||
for(var/i in 1 to min(5, length(turfs_to_convert)))
|
||||
var/turf/conversion_target = turfs_to_convert[length(turfs_to_convert)]
|
||||
turfs_to_convert.len--
|
||||
if(!isflockturf(conversion_target) && !isspaceturf(conversion_target))
|
||||
flock.claim_turf(conversion_target)
|
||||
|
||||
/obj/structure/flock/relay/proc/alert_organics()
|
||||
for(var/mob/M as anything in GLOB.player_list)
|
||||
var/turf/T = get_turf(M)
|
||||
if(is_station_level(T.z) && M.can_hear())
|
||||
M.playsound_local(M, 'sound/goonstation/flockmind/Flock_Reactor.ogg', 30, FALSE)
|
||||
to_chat(M, SPAN_FLOCKSAY("<b>A horrible, otherworldly wave eminates from the <i>[dir2text(get_dir(get_turf(M), loc))]</i>."))
|
||||
|
||||
/obj/structure/flock/relay/proc/announce_relay()
|
||||
var/message = Gibberish("The Signal is coming.", 70, replace_rate = 50)
|
||||
GLOB.major_announcement.Announce(message,
|
||||
"Hijacked Signal",
|
||||
'sound/effects/siren-spooky.ogg',
|
||||
new_sound2 = 'sound/AI/aimalf.ogg'
|
||||
)
|
||||
|
||||
/// GG
|
||||
/obj/structure/flock/relay/proc/lorimer_burst()
|
||||
set waitfor = FALSE
|
||||
flock_won_da_game = TRUE
|
||||
flock.set_flock_game_status(FLOCK_ENDGAME_RELAY_ACTIVATING)
|
||||
|
||||
log_game("The Flock ([flock?.name || "NULL"]) has successfully broadcast The Signal at [AREACOORD(src)].")
|
||||
update_appearance(UPDATE_OVERLAYS)
|
||||
|
||||
flock_talk(null, "!!! TRASMITTING SIGNAL !!!", flock)
|
||||
visible_message("<span size=5>[gradient_text("[src] begins sparking wildly! The air is charged with static!", "#3cb5a3", "#124e43")]</span>")
|
||||
soundloop.stop()
|
||||
qdel(counter)
|
||||
|
||||
for(var/mob/M as anything in GLOB.player_list)
|
||||
if(M.can_hear())
|
||||
M.playsound_local(M, 'sound/goonstation/flockmind/flock_broadcast_charge.ogg', 30, FALSE)
|
||||
|
||||
sleep(20 SECONDS)
|
||||
|
||||
for(var/mob/M as anything in GLOB.player_list)
|
||||
if(M.can_hear())
|
||||
M.playsound_local(M, 'sound/goonstation/flockmind/flock_broadcast_kaboom.ogg', 30, FALSE)
|
||||
|
||||
if(isliving(M))
|
||||
var/mob/living/L = M
|
||||
L.flash_eyes(3, TRUE, TRUE, TRUE)
|
||||
|
||||
sleep(2 SECONDS)
|
||||
|
||||
flock.set_flock_game_status(FLOCK_ENDGAME_VICTORY)
|
||||
explosion(src, 30, 60, 80, 100, ignorecap = TRUE, cause = src)
|
||||
|
||||
sleep(2 SECONDS)
|
||||
|
||||
for(var/obj/machinery/telecomms/T in SSmachines.get_by_type(/obj/machinery/telecomms))
|
||||
T.emp_act(EMP_HEAVY)
|
||||
|
||||
SSshuttle.emergency.request(null, 0.3)
|
||||
SSshuttle.emergency.canRecall = FALSE // Cannot recall
|
||||
@@ -0,0 +1,63 @@
|
||||
/obj/structure/flock/rift
|
||||
name = "static rift"
|
||||
desc = "That doesn't look human."
|
||||
flock_desc = "The rift through which your Flock will enter this world."
|
||||
flock_id = "Entry Rift"
|
||||
icon_state = "rift"
|
||||
|
||||
density = FALSE
|
||||
max_integrity = 200
|
||||
|
||||
build_time = 10 SECONDS
|
||||
no_flock_decon = TRUE
|
||||
|
||||
/obj/structure/flock/rift/Initialize(mapload, datum/flock/join_flock)
|
||||
. = ..()
|
||||
var/turf/T = get_turf(src)
|
||||
playsound(T, 'sound/goonstation/flockmind/ArtifactFea3.ogg', 50, FALSE, use_reverb = FALSE)
|
||||
|
||||
/obj/structure/flock/rift/update_info_tag()
|
||||
info_tag.set_text("Entry Time: [build_time_left()] seconds")
|
||||
|
||||
/obj/structure/flock/rift/finish_building()
|
||||
. = ..()
|
||||
|
||||
for(var/i in 1 to 4)
|
||||
new /obj/structure/flock/egg(src, flock)
|
||||
|
||||
// 1 flockbit
|
||||
new /obj/structure/flock/egg/bit(src, flock)
|
||||
|
||||
var/list/convertable_turfs = list()
|
||||
for(var/turf/simulated/T in RANGE_TURFS(2, src))
|
||||
if(!isflockturf(T) && T.can_flock_convert())
|
||||
convertable_turfs += T
|
||||
|
||||
shuffle_inplace(convertable_turfs)
|
||||
convertable_turfs.len = min(convertable_turfs.len, 16)
|
||||
|
||||
// Convert turfs
|
||||
for(var/turf/simulated/T as anything in convertable_turfs)
|
||||
if(flock)
|
||||
flock.claim_turf(T)
|
||||
else
|
||||
flock_convert_turf(T)
|
||||
|
||||
// Spawn sentinels
|
||||
var/sentinel_count = 0
|
||||
for(var/turf/simulated/floor/flock/T in convertable_turfs)
|
||||
if(!T.can_flock_occupy())
|
||||
continue
|
||||
|
||||
new /obj/structure/flock/sentinel(T, flock)
|
||||
sentinel_count++
|
||||
if(sentinel_count == 2)
|
||||
break
|
||||
|
||||
// Spread contents
|
||||
var/turf/turfloc = get_turf(src)
|
||||
for(var/atom/movable/AM as anything in src)
|
||||
AM.forceMove(turfloc)
|
||||
AM.throw_at(get_random_perimeter_turf(turfloc, 10), 10, 3, spin = FALSE)
|
||||
|
||||
qdel(src)
|
||||
@@ -0,0 +1,177 @@
|
||||
#define NOT_CHARGED -1
|
||||
#define LOSING_CHARGE 0
|
||||
#define CHARGING 1
|
||||
#define CHARGED 2
|
||||
|
||||
/obj/structure/flock/sentinel
|
||||
name = "glowing pylon"
|
||||
desc = "A glowing pylon of sorts, faint sparks are jumping inside of it."
|
||||
flock_desc = "A charged pylon, capable of sending disorienting arcs of electricity at enemies. Consumes 20 compute."
|
||||
icon_state = "sentinel"
|
||||
|
||||
max_integrity = 80
|
||||
|
||||
flock_id = "Sentinel"
|
||||
active_bandwidth_cost = 20
|
||||
|
||||
resource_cost = 150
|
||||
|
||||
/// Attacks require charging
|
||||
var/datum/point_holder/charge
|
||||
/// Charge gained per second while a target is in range
|
||||
var/charge_per_second = 10
|
||||
/// Trend of charge.
|
||||
var/charge_status = NOT_CHARGED
|
||||
/// Turret range
|
||||
var/range = 4
|
||||
/// Damage per zap.
|
||||
var/damage_per_zap = 5
|
||||
|
||||
/obj/structure/flock/sentinel/Initialize(mapload, joinflock)
|
||||
charge = new
|
||||
charge.set_max_points(100)
|
||||
. = ..()
|
||||
AddComponent(/datum/component/flock_protection)
|
||||
START_PROCESSING(SSobj, src)
|
||||
|
||||
/obj/structure/flock/sentinel/Destroy(force)
|
||||
STOP_PROCESSING(SSobj, src)
|
||||
QDEL_NULL(charge)
|
||||
return ..()
|
||||
|
||||
/obj/structure/flock/sentinel/process(seconds_per_tick)
|
||||
if(isnull(flock))
|
||||
set_active(FALSE)
|
||||
return PROCESS_KILL
|
||||
|
||||
// Check if the flock can continue to run the sentinel
|
||||
if(active)
|
||||
if(flock.available_bandwidth() < 0)
|
||||
set_active(FALSE)
|
||||
else
|
||||
if(flock.can_afford(active_bandwidth_cost))
|
||||
set_active(TRUE)
|
||||
|
||||
if(!active)
|
||||
if(charge.has_points())
|
||||
charge.adjust_points((charge_per_second / 2) * seconds_per_tick)
|
||||
update_info_tag()
|
||||
charge_status = LOSING_CHARGE
|
||||
else
|
||||
charge_status = NOT_CHARGED
|
||||
update_appearance(UPDATE_ICON_STATE)
|
||||
return
|
||||
|
||||
// Gain more charge
|
||||
if(charge_status != CHARGED)
|
||||
charge.adjust_points(charge_per_second * seconds_per_tick)
|
||||
update_info_tag()
|
||||
if(charge.has_points(100))
|
||||
charge_status = CHARGED
|
||||
update_appearance(UPDATE_ICON_STATE)
|
||||
else if(charge_status != CHARGING)
|
||||
charge_status = CHARGING
|
||||
update_appearance(UPDATE_ICON_STATE)
|
||||
return
|
||||
|
||||
// If the flock has no enemies, don't bother getting a target.
|
||||
if(!length(flock.enemies))
|
||||
return
|
||||
|
||||
// Select target
|
||||
var/mob/living/target
|
||||
for(var/mob/living/L in viewers(range, src))
|
||||
|
||||
if(HAS_TRAIT(L, TRAIT_SHOCKED_BY_SENTINEL) || !flock.is_mob_enemy(L))
|
||||
continue
|
||||
|
||||
if(L.stat != CONSCIOUS || L.incapacitated(ignore_restraints = TRUE, ignore_grab = TRUE))
|
||||
continue
|
||||
|
||||
target = L
|
||||
break
|
||||
|
||||
// No target, abort.
|
||||
if(isnull(target))
|
||||
return
|
||||
|
||||
charge_status = CHARGING
|
||||
charge.adjust_points(-100)
|
||||
update_info_tag()
|
||||
|
||||
target.electrocute_act(30, src, 1)
|
||||
addtimer(CALLBACK(src, PROC_REF(clear_trait), target), 2 SECONDS)
|
||||
|
||||
Beam(target, "lightning[rand(1, 12)]", 'icons/effects/effects.dmi', 0.5 SECONDS, INFINITY)
|
||||
playsound(loc, 'sound/magic/lightningbolt.ogg', 60, TRUE)
|
||||
|
||||
target.visible_message(
|
||||
SPAN_DANGER("<b>[target]</b> is struck by a bolt of energy arcing off of <b>[src]</b>."),
|
||||
blind_message = SPAN_HEAR("You hear a loud electrical crackle."),
|
||||
)
|
||||
|
||||
var/list/hit_mobs = list(target)
|
||||
var/mob/previous_hit = target
|
||||
var/end_of_chain = TRUE
|
||||
|
||||
for(var/i in 1 to 3)
|
||||
end_of_chain = TRUE
|
||||
|
||||
for(var/mob/living/M in viewers(2, get_turf(previous_hit)))
|
||||
if((M in hit_mobs) || !flock.is_mob_enemy(M))
|
||||
continue
|
||||
|
||||
if(M.stat == DEAD || M.incapacitated(ignore_restraints = TRUE, ignore_grab = TRUE))
|
||||
continue
|
||||
|
||||
end_of_chain = FALSE
|
||||
|
||||
hit_mobs += M
|
||||
M.electrocute_act(20, src, 0)
|
||||
previous_hit.Beam(M, "lightning[rand(1, 12)]", 'icons/effects/effects.dmi', 0.5 SECONDS, INFINITY)
|
||||
target.visible_message(
|
||||
SPAN_DANGER("<b>[M]</b> is struck by a bolt of energy arcing off of <b>[previous_hit]</b>."),
|
||||
blind_message = SPAN_HEAR("You hear a loud electrical crackle."),
|
||||
)
|
||||
|
||||
previous_hit = M
|
||||
log_attack(src, target, "damages with an arc chain owned by [flock.name]")
|
||||
break
|
||||
|
||||
if(end_of_chain)
|
||||
break
|
||||
|
||||
/obj/structure/flock/sentinel/update_icon_state()
|
||||
if(!active || charge_status == NOT_CHARGED)
|
||||
icon_state = "sentinel"
|
||||
else
|
||||
icon_state = "sentinelon"
|
||||
return ..()
|
||||
|
||||
/obj/structure/flock/sentinel/flock_structure_examine(mob/user)
|
||||
var/charge_status_str
|
||||
switch(charge_status)
|
||||
if(NOT_CHARGED)
|
||||
charge_status_str = "Idle"
|
||||
if(LOSING_CHARGE)
|
||||
charge_status_str = "Losing charge"
|
||||
if(CHARGING)
|
||||
charge_status_str = "Charging"
|
||||
if(CHARGED)
|
||||
charge_status_str = "Charged"
|
||||
|
||||
return list(
|
||||
SPAN_FLOCKSAY("<b>Status:</b> [charge_status_str]"),
|
||||
SPAN_FLOCKSAY("<b>Charge Percentage: [charge.has_points()]%"),
|
||||
)
|
||||
|
||||
/obj/structure/flock/sentinel/update_info_tag()
|
||||
info_tag.set_text("Charge: [charge.has_points()]%")
|
||||
|
||||
/obj/structure/flock/sentinel/proc/clear_trait(mob/living/target)
|
||||
REMOVE_TRAIT(target, TRAIT_SHOCKED_BY_SENTINEL, ref(src))
|
||||
|
||||
#undef NOT_CHARGED
|
||||
#undef LOSING_CHARGE
|
||||
#undef CHARGING
|
||||
#undef CHARGED
|
||||
@@ -0,0 +1,75 @@
|
||||
/obj/structure/flock/tealprint
|
||||
name = "building ghost"
|
||||
desc = "It's some weird looking ghost building. Seems like its under construction, You can see faint strands of material floating in it."
|
||||
|
||||
density = FALSE
|
||||
no_flock_decon = TRUE
|
||||
|
||||
flock_id = "Construction Tealprint" // if you change this update Flockpanel.tsx!!!!!
|
||||
|
||||
var/datum/point_holder/tealprint/substrate
|
||||
|
||||
var/obj/structure/flock/building_type = null
|
||||
|
||||
/obj/structure/flock/tealprint/Initialize(mapload, datum/flock/join_flock, obj/structure/flock/desired_type)
|
||||
. = ..()
|
||||
var/turf/T = loc
|
||||
if(!T.can_flock_occupy(src))
|
||||
return INITIALIZE_HINT_QDEL
|
||||
|
||||
building_type = desired_type
|
||||
icon = initial(building_type.icon)
|
||||
icon_state = initial(building_type.icon_state)
|
||||
alpha = 104
|
||||
|
||||
substrate = new()
|
||||
substrate.set_max_points(initial(desired_type.resource_cost))
|
||||
substrate.owner = src
|
||||
|
||||
/obj/structure/flock/tealprint/Destroy()
|
||||
QDEL_NULL(substrate)
|
||||
return ..()
|
||||
|
||||
/obj/structure/flock/tealprint/update_info_tag()
|
||||
info_tag?.set_text("Substrate: [substrate.has_points()] / [substrate.get_max_points()]")
|
||||
|
||||
/obj/structure/flock/tealprint/flock_structure_examine(mob/user)
|
||||
return list(
|
||||
SPAN_FLOCKSAY("<b>Construction Percentage:</b> [floor(substrate.has_points() / substrate.get_max_points() * 100)]"),
|
||||
SPAN_FLOCKSAY("<b>Construction Progress:</b> [substrate.has_points()] added, [substrate.get_max_points()] needed")
|
||||
)
|
||||
|
||||
/// Complete the structure.
|
||||
/obj/structure/flock/tealprint/proc/complete_structure()
|
||||
var/obj/structure/flock/structure = new building_type(get_turf(src))
|
||||
flock_talk(src, "Tealprint for [structure.flock_id] realized.", flock, involuntary = TRUE)
|
||||
qdel(src)
|
||||
|
||||
/// Attempt to cancel the construction, spitting out the substrate. Some structures cannot be cancelled.
|
||||
/obj/structure/flock/tealprint/proc/try_cancel_structure()
|
||||
if(!initial(building_type.cancellable))
|
||||
return FALSE
|
||||
|
||||
if(substrate.has_points())
|
||||
var/obj/item/flock_cube/cube = new(drop_location())
|
||||
cube.substrate = substrate.has_points()
|
||||
|
||||
flock_talk(src, "Tealprint dematerializing", flock, involuntary = TRUE)
|
||||
playsound(src, 'sound/goonstation/flockmind/flockdrone_door_deny.ogg', 30, TRUE, extrarange = -10)
|
||||
qdel(src)
|
||||
return TRUE
|
||||
|
||||
/datum/point_holder/tealprint
|
||||
var/obj/structure/flock/tealprint/owner
|
||||
|
||||
/datum/point_holder/tealprint/Destroy(force, ...)
|
||||
owner = null
|
||||
return ..()
|
||||
|
||||
/datum/point_holder/tealprint/add_points(num)
|
||||
. = ..()
|
||||
if(.)
|
||||
if(is_full())
|
||||
owner.complete_structure()
|
||||
else
|
||||
owner.update_info_tag()
|
||||
@@ -0,0 +1,136 @@
|
||||
/obj/structure/flock/gnesis_turret
|
||||
name = "fluid vat"
|
||||
desc = "A vat of bubbling teal fluid, covered in hollow spikes."
|
||||
flock_desc = "A turret that fires gnesis-filled spikes at enemies, beginning their conversion to Flockbits. Consumes 50 bandwidth passively."
|
||||
icon_state = "teleblocker-off"
|
||||
|
||||
max_integrity = 80
|
||||
|
||||
flock_id = "Gnesis turret"
|
||||
resource_cost = 150
|
||||
active_bandwidth_cost = 50
|
||||
|
||||
var/range = 8
|
||||
var/projectile_count = 4
|
||||
var/projectile_type = /obj/projectile/bullet/dart/piercing/gnesis
|
||||
|
||||
var/current_target
|
||||
|
||||
/obj/structure/flock/gnesis_turret/Initialize(mapload, datum/flock/join_flock)
|
||||
. = ..()
|
||||
AddComponent(/datum/component/flock_protection)
|
||||
START_PROCESSING(SSobj, src)
|
||||
|
||||
/obj/structure/flock/gnesis_turret/Destroy()
|
||||
if(prob(25))
|
||||
new /obj/item/reagent_containers/glass/gnesis/prefilled(get_turf(src))
|
||||
set_target(null)
|
||||
STOP_PROCESSING(SSobj, src)
|
||||
return ..()
|
||||
|
||||
/obj/structure/flock/gnesis_turret/update_icon_state()
|
||||
if(active)
|
||||
icon_state = "teleblocker-on"
|
||||
else
|
||||
icon_state = "teleblocker-off"
|
||||
. = ..()
|
||||
|
||||
/obj/structure/flock/gnesis_turret/process()
|
||||
if(isnull(flock))
|
||||
set_active(FALSE)
|
||||
return PROCESS_KILL
|
||||
|
||||
if(active)
|
||||
if(flock.available_bandwidth() < 0)
|
||||
set_active(FALSE)
|
||||
else
|
||||
if(flock.can_afford(active_bandwidth_cost))
|
||||
set_active(TRUE)
|
||||
|
||||
if(!active)
|
||||
return
|
||||
|
||||
if(isnull(current_target) || !is_valid_target(current_target))
|
||||
set_target(find_target())
|
||||
if(isnull(current_target))
|
||||
return
|
||||
|
||||
fire()
|
||||
|
||||
/obj/structure/flock/gnesis_turret/proc/fire(bullets = src.projectile_count)
|
||||
if(isnull(current_target))
|
||||
return
|
||||
|
||||
var/obj/projectile/boolet = new projectile_type
|
||||
boolet.preparePixelProjectile(current_target, loc, deviation = rand(-5, 5))
|
||||
boolet.firer = src
|
||||
boolet.fire()
|
||||
|
||||
bullets--
|
||||
if(bullets)
|
||||
addtimer(CALLBACK(src, PROC_REF(fire), bullets), 0.1 SECONDS)
|
||||
|
||||
/obj/structure/flock/gnesis_turret/proc/find_target()
|
||||
var/list/targets = list()
|
||||
|
||||
for(var/mob/living/L in viewers(range, src))
|
||||
if(is_valid_target(L))
|
||||
targets += L
|
||||
|
||||
for(var/obj/mecha/M in viewers(range, src))
|
||||
targets += M
|
||||
|
||||
var/target = null
|
||||
var/target_dist = INFINITY
|
||||
for(var/L as anything in targets)
|
||||
var/dist = get_dist(src, L)
|
||||
if(dist < target_dist)
|
||||
target = L
|
||||
target_dist = dist
|
||||
|
||||
return target
|
||||
|
||||
/obj/structure/flock/gnesis_turret/proc/is_valid_target(target)
|
||||
if(!can_see(src, target, range))
|
||||
return FALSE
|
||||
|
||||
if(ismecha(target))
|
||||
return TRUE
|
||||
|
||||
var/mob/living/L = target
|
||||
|
||||
if(isflockmob(L))
|
||||
return FALSE
|
||||
|
||||
if(L.stat != CONSCIOUS)
|
||||
return FALSE
|
||||
|
||||
if(!flock.is_mob_enemy(L))
|
||||
return FALSE
|
||||
|
||||
if(L.incapacitated(ignore_restraints = TRUE, ignore_grab = TRUE))
|
||||
return FALSE
|
||||
|
||||
return TRUE
|
||||
|
||||
/obj/structure/flock/gnesis_turret/proc/set_target(mob/new_target)
|
||||
if(current_target)
|
||||
UnregisterSignal(current_target, COMSIG_PARENT_QDELETING)
|
||||
|
||||
current_target = new_target
|
||||
|
||||
if(current_target)
|
||||
RegisterSignal(current_target, COMSIG_PARENT_QDELETING, PROC_REF(target_del))
|
||||
|
||||
/obj/structure/flock/gnesis_turret/proc/target_del(datum/source)
|
||||
SIGNAL_HANDLER
|
||||
|
||||
set_target(null)
|
||||
|
||||
/obj/projectile/bullet/dart/piercing/gnesis
|
||||
name = "barbed crystalline spike"
|
||||
desc = "A hollow teal crystal, like some sort of weird alien syringe. It has a barbed tip. Nasty!"
|
||||
|
||||
/obj/projectile/bullet/dart/piercing/gnesis/Initialize(mapload)
|
||||
. = ..()
|
||||
reagents.add_reagent(/datum/reagent/gnesis, 2)
|
||||
@@ -0,0 +1,68 @@
|
||||
/obj/structure/window/flock
|
||||
icon = 'icons/goonstation/mob/featherzone.dmi'
|
||||
pass_flags = PASSFLOCK
|
||||
heat_resistance = 32000
|
||||
glass_type = /obj/item/stack/sheet/gnesis_glass
|
||||
|
||||
/obj/structure/window/flock/Initialize(mapload, direct)
|
||||
. = ..()
|
||||
AddComponent(/datum/component/flock_object)
|
||||
AddComponent(/datum/component/flock_protection, report_unarmed=FALSE)
|
||||
var/static/list/loc_connections = list(
|
||||
COMSIG_ATOM_ENTERED = PROC_REF(on_crossed),
|
||||
)
|
||||
AddElement(/datum/element/connect_loc, loc_connections)
|
||||
ADD_TRAIT(src, TRAIT_FLOCK_THING, INNATE_TRAIT)
|
||||
|
||||
/obj/structure/window/flock/proc/on_crossed(atom/source, atom/movable/crosser)
|
||||
SIGNAL_HANDLER
|
||||
|
||||
if(!isflockdrone(crosser))
|
||||
return
|
||||
|
||||
if(!HAS_TRAIT(crosser, TRAIT_FLOCKPHASE))
|
||||
animate_flockpass(crosser)
|
||||
|
||||
/obj/structure/window/flock/examine(mob/user)
|
||||
if(!isflockmob(user))
|
||||
return ..()
|
||||
|
||||
. = list(
|
||||
SPAN_FLOCKSAY("<b>###=- Ident confirmed, data packet received.</b>"),
|
||||
SPAN_FLOCKSAY("<b>ID:</b> Transparent Barrier"),
|
||||
SPAN_FLOCKSAY("<b>System Integrity:</b> [get_integrity_percentage()]%"),
|
||||
SPAN_FLOCKSAY("<b>###=-</b>")
|
||||
)
|
||||
|
||||
/obj/structure/window/flock/CanPass(atom/movable/mover, border_dir)
|
||||
. = ..()
|
||||
if(.)
|
||||
return .
|
||||
|
||||
if(!isflockdrone(mover))
|
||||
return .
|
||||
|
||||
var/mob/living/basic/flock/drone/bird = mover
|
||||
if(HAS_TRAIT(bird, TRAIT_FLOCKPHASE))
|
||||
return TRUE
|
||||
|
||||
if(bird.can_flockphase())
|
||||
return TRUE
|
||||
|
||||
/obj/structure/window/flock/CanPathfindPass(to_dir, datum/can_pass_info/pass_info)
|
||||
var/atom/M = locateUID(pass_info.caller_uid)
|
||||
if(!M)
|
||||
return ..()
|
||||
if(!isflockdrone(M))
|
||||
return ..()
|
||||
var/mob/living/basic/flock/drone/bird = M
|
||||
if(!bird.can_flockphase())
|
||||
return ..()
|
||||
return TRUE
|
||||
|
||||
/obj/structure/window/flock/fulltile
|
||||
dir = SOUTHWEST
|
||||
fulltile = TRUE
|
||||
|
||||
/obj/structure/window/flock/try_flock_convert(datum/flock/flock, force)
|
||||
return
|
||||
@@ -0,0 +1,25 @@
|
||||
/datum/ai_planning_subtree/goap/flockdrone
|
||||
possible_behaviors = list(
|
||||
/datum/ai_behavior/flock/stare,
|
||||
/datum/ai_behavior/flock/wander,
|
||||
/datum/ai_behavior/flock/resist,
|
||||
/datum/ai_behavior/flock/find_conversion_target,
|
||||
/datum/ai_behavior/flock/find_deconstruct_target,
|
||||
/datum/ai_behavior/flock/find_heal_target,
|
||||
/datum/ai_behavior/flock/find_existing_nest,
|
||||
/datum/ai_behavior/flock/find_harvest_target,
|
||||
/datum/ai_behavior/flock/find_closed_container,
|
||||
/datum/ai_behavior/flock/find_storage_item,
|
||||
/datum/ai_behavior/flock/find_conversion_target/nest,
|
||||
/datum/ai_behavior/flock/attack_target,
|
||||
/datum/ai_behavior/flock/find_capture_target,
|
||||
/datum/ai_behavior/flock/find_deposit_target,
|
||||
)
|
||||
|
||||
/datum/ai_planning_subtree/goap/flock
|
||||
possible_behaviors = list(
|
||||
/datum/ai_behavior/flock/stare,
|
||||
/datum/ai_behavior/flock/wander,
|
||||
/datum/ai_behavior/flock/resist,
|
||||
/datum/ai_behavior/flock/find_conversion_target,
|
||||
)
|
||||
@@ -0,0 +1,69 @@
|
||||
/turf/simulated/floor/flock
|
||||
name = "weird floor"
|
||||
icon = 'icons/goonstation/mob/featherzone.dmi'
|
||||
icon_state = "floor"
|
||||
base_icon_state = "floor"
|
||||
|
||||
light_color = "#19b299"
|
||||
light_power = 0.4
|
||||
light_range = 4
|
||||
|
||||
var/is_on = FALSE
|
||||
|
||||
/// List of mobs flockrunning on this turf, lazylist.
|
||||
var/list/mob/living/basic/flock/flockrunning_mobs
|
||||
/// List of pylons powering this turf, lazylist.
|
||||
var/list/obj/structure/flock/collector/connected_pylons
|
||||
|
||||
/turf/simulated/floor/flock/Initialize(mapload)
|
||||
. = ..()
|
||||
ADD_TRAIT(src, TRAIT_FLOCK_THING, INNATE_TRAIT)
|
||||
AddComponent(/datum/component/flock_protection, FALSE, TRUE, FALSE, FALSE)
|
||||
|
||||
/turf/simulated/floor/flock/Destroy(force)
|
||||
REMOVE_TRAIT(src, TRAIT_FLOCK_NODECON, INNATE_TRAIT) // Turfs dont disappear!!!
|
||||
qdel(GetComponent(/datum/component/flock_protection))
|
||||
connected_pylons = null
|
||||
|
||||
for(var/mob/living/basic/flock/drone/bird in flockrunning_mobs)
|
||||
if(HAS_TRAIT(bird, TRAIT_FLOCKPHASE))
|
||||
bird.stop_flockphase(TRUE)
|
||||
|
||||
flockrunning_mobs = null
|
||||
return ..()
|
||||
|
||||
/turf/simulated/floor/flock/get_flock_id()
|
||||
return "Conduit"
|
||||
|
||||
/turf/simulated/floor/flock/try_replace_tile(obj/item/stack/tile/T, mob/user, params)
|
||||
return FALSE
|
||||
|
||||
/turf/simulated/floor/flock/burn_tile()
|
||||
return
|
||||
|
||||
/turf/simulated/floor/flock/update_icon_state()
|
||||
if(broken)
|
||||
icon_state = "[base_icon_state]-broken"
|
||||
else
|
||||
if(is_on)
|
||||
icon_state = "[base_icon_state]-on"
|
||||
else
|
||||
icon_state = base_icon_state
|
||||
return ..()
|
||||
|
||||
/// Turns the tile on or off depending on what state it should be in.
|
||||
/turf/simulated/floor/flock/proc/update_power()
|
||||
var/should_be_on = FALSE
|
||||
if(!broken && (length(connected_pylons) || length(flockrunning_mobs)))
|
||||
should_be_on = TRUE
|
||||
|
||||
if(should_be_on == is_on)
|
||||
return
|
||||
|
||||
is_on = should_be_on
|
||||
if(is_on)
|
||||
set_light(4, 0.4, light_color)
|
||||
else
|
||||
set_light(0, 0, light_color)
|
||||
|
||||
update_appearance(UPDATE_ICON_STATE)
|
||||
@@ -0,0 +1,47 @@
|
||||
/turf/simulated/wall/flock
|
||||
name = "cybernetic wall"
|
||||
icon = 'icons/goonstation/turf/flock.dmi'
|
||||
icon_state = "flock-0"
|
||||
base_icon_state = "flock"
|
||||
|
||||
pathing_pass_method = TURF_PATHING_PASS_PROC
|
||||
|
||||
light_color = "#19b299"
|
||||
light_power = 0.5
|
||||
light_range = 0.6
|
||||
|
||||
/turf/simulated/wall/flock/Initialize(mapload)
|
||||
. = ..()
|
||||
ADD_TRAIT(src, TRAIT_FLOCK_THING, INNATE_TRAIT)
|
||||
AddComponent(/datum/component/flock_protection, FALSE, TRUE, FALSE, FALSE)
|
||||
|
||||
/turf/simulated/wall/flock/Destroy(force)
|
||||
REMOVE_TRAIT(src, TRAIT_FLOCK_THING, INNATE_TRAIT) // Turfs are persistent refs
|
||||
qdel(GetComponent(/datum/component/flock_protection))
|
||||
return ..()
|
||||
|
||||
/turf/simulated/wall/flock/CanPass(atom/movable/mover, border_dir)
|
||||
. = ..()
|
||||
if(.)
|
||||
return
|
||||
|
||||
if(!isflockdrone(mover))
|
||||
return
|
||||
|
||||
var/mob/living/basic/flock/drone/bird = mover
|
||||
if(HAS_TRAIT(bird, TRAIT_FLOCKPHASE))
|
||||
return TRUE
|
||||
|
||||
if(bird.can_flockphase())
|
||||
return TRUE
|
||||
|
||||
/turf/simulated/wall/flock/CanPathfindPass(to_dir, datum/can_pass_info/pass_info)
|
||||
var/atom/M = locateUID(pass_info.caller_uid)
|
||||
if(!M)
|
||||
return ..()
|
||||
if(!isflockdrone(M))
|
||||
return ..()
|
||||
var/mob/living/basic/flock/drone/bird = M
|
||||
if(!bird.can_flockphase())
|
||||
return ..()
|
||||
return TRUE
|
||||
@@ -0,0 +1,50 @@
|
||||
/datum/flock_unlockable
|
||||
// auto populated field
|
||||
var/name
|
||||
// auto populated field
|
||||
var/purchase_cost
|
||||
|
||||
var/obj/structure/flock/structure_type
|
||||
|
||||
var/unlocked = FALSE
|
||||
|
||||
/datum/flock_unlockable/New()
|
||||
purchase_cost = initial(structure_type.active_bandwidth_cost)
|
||||
name = "[initial(structure_type.flock_id)] ([purchase_cost])"
|
||||
|
||||
/datum/flock_unlockable/proc/refresh_lock_status(datum/flock/flock, total_compute, available_compute)
|
||||
if(is_unlockable(flock, total_compute, available_compute))
|
||||
if(!unlocked)
|
||||
unlock(flock)
|
||||
|
||||
else if(unlocked)
|
||||
lock(flock)
|
||||
|
||||
/datum/flock_unlockable/proc/is_unlockable(datum/flock/flock, total_compute, available_compute)
|
||||
return TRUE
|
||||
|
||||
/datum/flock_unlockable/proc/unlock(datum/flock/flock)
|
||||
unlocked = TRUE
|
||||
flock_talk(null, "New structure devised: [initial(structure_type.flock_id)]", flock, TRUE)
|
||||
|
||||
/datum/flock_unlockable/proc/lock(datum/flock/flock)
|
||||
unlocked = FALSE
|
||||
flock_talk(null, "Alert, structure tealprint disabled: [initial(structure_type.flock_id)]", flock, TRUE)
|
||||
|
||||
/datum/flock_unlockable/sentinel
|
||||
structure_type = /obj/structure/flock/sentinel
|
||||
|
||||
/datum/flock_unlockable/collector
|
||||
structure_type = /obj/structure/flock/collector
|
||||
|
||||
/datum/flock_unlockable/interceptor
|
||||
structure_type = /obj/structure/flock/interceptor
|
||||
|
||||
/datum/flock_unlockable/turret
|
||||
structure_type = /obj/structure/flock/gnesis_turret
|
||||
|
||||
/datum/flock_unlockable/relay
|
||||
structure_type = /obj/structure/flock/relay
|
||||
|
||||
/datum/flock_unlockable/relay/is_unlockable(datum/flock/flock, total_compute, available_compute)
|
||||
return flock.total_bandwidth() >= FLOCK_COMPUTE_COST_RELAY && flock.total_turfs() >= FLOCK_TURFS_FOR_RELAY && flock.flock_game_status == NONE
|
||||
@@ -0,0 +1,87 @@
|
||||
/datum/reagent/gnesis
|
||||
name = "coagulated gnesis"
|
||||
id = "gnesis_tox"
|
||||
description = "A thick teal fluid of alien origin. It moves in ways that suggest it might be alive in some way."
|
||||
color = "#4d736d"
|
||||
taste_description = "oily, with a sweet aftertaste"
|
||||
metabolization_rate = 0.2
|
||||
process_flags = ORGANIC | SYNTHETIC
|
||||
/// The color of the light to give off
|
||||
var/light_color = "#26ffe6"
|
||||
/// Reagent -> Gnesis rate per 2 seconds
|
||||
var/conversion_rate = 0.4
|
||||
/// Amount of gnesis required to turn the victim into a flockbit
|
||||
var/flocksplosion_threshold = 200
|
||||
/// Do not convert these reagents
|
||||
var/list/do_not_convert = list("charcoal", "calomel", "pen_acid", "gnesis_tox")
|
||||
|
||||
/datum/reagent/gnesis/on_new(data)
|
||||
..()
|
||||
START_PROCESSING(SSprocessing, src)
|
||||
|
||||
/datum/reagent/gnesis/Destroy()
|
||||
STOP_PROCESSING(SSprocessing, src)
|
||||
return ..()
|
||||
|
||||
/datum/reagent/gnesis/on_mob_life(mob/living/M)
|
||||
if(volume > 20) // 2.5 bursts of the turret
|
||||
if(!ismachineperson(M))
|
||||
M.blood_volume -= conversion_rate
|
||||
M.reagents.add_reagent("gnesis_tox", conversion_rate)
|
||||
|
||||
if(volume > flocksplosion_threshold)
|
||||
new /mob/living/basic/flock/bit(get_turf(M))
|
||||
M.gib()
|
||||
return
|
||||
|
||||
if(volume < flocksplosion_threshold / 2)
|
||||
M.set_light(0, 0, COLOR_WHITE)
|
||||
if(prob(5))
|
||||
M.playsound_local(get_turf(M), pick(list('sound/goonstation/flockmind/flockbit_wisp1.ogg',
|
||||
'sound/goonstation/flockmind/flockbit_wisp2.ogg',
|
||||
'sound/goonstation/flockmind/flockbit_wisp3.ogg',
|
||||
'sound/goonstation/flockmind/flockbit_wisp4.ogg',
|
||||
'sound/goonstation/flockmind/flockbit_wisp5.ogg',
|
||||
'sound/goonstation/flockmind/flockbit_wisp6.ogg')), 40, FALSE, use_reverb = FALSE)
|
||||
if(prob(2))
|
||||
to_chat(M, SPAN_FLOCKSAY("You feel your blood running cold."))
|
||||
else
|
||||
if(prob(2) && ishuman(M))
|
||||
var/mob/living/carbon/human/H = M
|
||||
H.take_organ_damage(5)
|
||||
if(prob(10))
|
||||
M.playsound_local(get_turf(M), pick(list('sound/goonstation/flockmind/flockbit_wisp1.ogg',
|
||||
'sound/goonstation/flockmind/flockbit_wisp2.ogg',
|
||||
'sound/goonstation/flockmind/flockbit_wisp3.ogg',
|
||||
'sound/goonstation/flockmind/flockbit_wisp4.ogg',
|
||||
'sound/goonstation/flockmind/flockbit_wisp5.ogg',
|
||||
'sound/goonstation/flockmind/flockbit_wisp6.ogg')), 40, FALSE, use_reverb = FALSE)
|
||||
if(prob(15))
|
||||
M.set_light(2, 0.2, light_color)
|
||||
if(prob(5))
|
||||
to_chat(M, SPAN_FLOCKSAY("You feel like something wants to burst out of your body!"))
|
||||
return ..()
|
||||
|
||||
/datum/reagent/gnesis/process()
|
||||
if(..())
|
||||
for(var/datum/reagent/R in holder.reagent_list)
|
||||
if(R.id in do_not_convert)
|
||||
continue
|
||||
holder.remove_reagent(R.id, conversion_rate)
|
||||
holder.add_reagent("gnesis_tox", conversion_rate)
|
||||
|
||||
/datum/reagent/gnesis/reaction_turf(turf/T, volume, color)
|
||||
. = ..()
|
||||
if(isspaceturf(T))
|
||||
return
|
||||
if(volume >= 50 && (isfloorturf(T) || iswallturf(T)))
|
||||
T.visible_message(SPAN_NOTICE("The substance flows out and sinks into [T], forming new shapes."))
|
||||
flock_convert_turf(T)
|
||||
return
|
||||
else if(volume >= 10)
|
||||
if(prob(50))
|
||||
new /obj/item/stack/sheet/gnesis(T)
|
||||
else
|
||||
new /obj/item/stack/sheet/gnesis_glass(T)
|
||||
return
|
||||
T.visible_message(SPAN_NOTICE("The substance flows out, spread too thinly."))
|
||||
Reference in New Issue
Block a user