mirror of
https://github.com/ParadiseSS13/Paradise.git
synced 2026-07-15 00:53:23 +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:
@@ -269,6 +269,9 @@
|
||||
/datum/action/cooldown/proc/PreActivate(atom/target)
|
||||
if(SEND_SIGNAL(owner, COMSIG_MOB_ABILITY_STARTED, src, target) & COMPONENT_BLOCK_ABILITY_START)
|
||||
return
|
||||
|
||||
if(!is_valid_target(target))
|
||||
return
|
||||
// Note, that PreActivate handles no cooldowns at all by default.
|
||||
// Be sure to call StartCooldown() in Activate() where necessary.
|
||||
. = Activate(target)
|
||||
@@ -361,4 +364,14 @@
|
||||
|
||||
return stat_panel_data
|
||||
|
||||
/**
|
||||
* Check if the target we're casting on is a valid target.
|
||||
* For no-target (self cast) actions, the target being checked (cast_on) is the caster.
|
||||
* For click_to_activate actions, the target being checked is the clicked atom.
|
||||
*
|
||||
* Return TRUE if cast_on is valid, FALSE otherwise
|
||||
*/
|
||||
/datum/action/cooldown/proc/is_valid_target(atom/cast_on)
|
||||
return TRUE
|
||||
|
||||
#undef COOLDOWN_NO_DISPLAY_TIME
|
||||
|
||||
@@ -2,12 +2,19 @@
|
||||
/datum/ai_behavior
|
||||
/// What distance you need to be from the target to perform the action.
|
||||
var/required_distance = 1
|
||||
/// If >0, overrides controller.target_search_radius
|
||||
var/search_radius_override = null
|
||||
/// Flags for extra behavior
|
||||
var/behavior_flags = NONE
|
||||
/// Cooldown between actions performances, defaults to the value of
|
||||
/// CLICK_CD_MELEE because that seemed like a nice standard for the speed of
|
||||
/// AI behavior
|
||||
var/action_cooldown = CLICK_CD_MELEE
|
||||
/// A multiplier applied to the behavior's goap_score().
|
||||
var/goap_weight = 1
|
||||
|
||||
/// Behaviors to add upon a successful setup
|
||||
var/list/sub_behaviors
|
||||
|
||||
/// Called by the AI controller when first being added. Additional arguments
|
||||
/// depend on the behavior type. For example, if the behavior involves attacking
|
||||
@@ -48,6 +55,7 @@
|
||||
controller.behavior_args -= type
|
||||
// If this was a movement task, reset our movement target if necessary
|
||||
if(!(behavior_flags & AI_BEHAVIOR_REQUIRE_MOVEMENT))
|
||||
next_behavior(controller, succeeded)
|
||||
return
|
||||
if(behavior_flags & AI_BEHAVIOR_KEEP_MOVE_TARGET_ON_FINISH)
|
||||
return
|
||||
@@ -63,3 +71,130 @@
|
||||
if(controller.movement_target_source != type)
|
||||
return
|
||||
controller.set_movement_target(type, null)
|
||||
|
||||
/// Returns a behavior to perform after this one, or null if continuing this one
|
||||
/datum/ai_behavior/proc/next_behavior(datum/ai_controller/controller, success)
|
||||
return null
|
||||
|
||||
/// Executed before goap_score(), to see if the behavior should even be considered.
|
||||
/datum/ai_behavior/proc/goap_precondition(datum/ai_controller/controller)
|
||||
return TRUE
|
||||
|
||||
/// Returns a numerical value that is essentially a priority for planner behaviors.
|
||||
/datum/ai_behavior/proc/goap_score(datum/ai_controller/controller)
|
||||
return score_distance(controller, goap_get_ideal_target(controller))
|
||||
|
||||
/// Returns the ideal target for this behavior.
|
||||
/datum/ai_behavior/proc/goap_get_ideal_target(datum/ai_controller/controller, set_path = FALSE)
|
||||
var/list/options = goap_filter_targets(controller)
|
||||
return get_best_target_by_distance_score(controller, options, set_path)
|
||||
|
||||
/// Filter through potential targets to find real targets.
|
||||
/datum/ai_behavior/proc/goap_filter_targets(datum/ai_controller/controller)
|
||||
var/list/options = list()
|
||||
for(var/atom/potential_target as anything in goap_get_potential_targets(controller))
|
||||
if(goap_is_valid_target(controller, potential_target))
|
||||
options += potential_target
|
||||
return options
|
||||
|
||||
/// Returns a list of potential targets to filter through.
|
||||
/datum/ai_behavior/proc/goap_get_potential_targets(datum/ai_controller/controller)
|
||||
return list()
|
||||
|
||||
/// Returns TRUE if the given atom is a valid target for this behavior.
|
||||
/datum/ai_behavior/proc/goap_is_valid_target(datum/ai_controller/controller, atom/target)
|
||||
return TRUE
|
||||
|
||||
#define BINARY_INSERT_TARGET(target_list, target, score) \
|
||||
do { \
|
||||
var/length = length(target_list); \
|
||||
if(!length) { \
|
||||
target_list[target] = score; \
|
||||
} else { \
|
||||
var/left = 1; \
|
||||
var/right = length; \
|
||||
var/middle = (left + right) >> 1; \
|
||||
while(left < right) { \
|
||||
if(target_list[target_list[middle]] <= score) { \
|
||||
left = middle + 1; \
|
||||
} else { \
|
||||
right = middle; \
|
||||
}; \
|
||||
middle = (left + right) >> 1; \
|
||||
}; \
|
||||
middle = target_list[target_list[middle]] > score ? middle : middle + 1; \
|
||||
target_list.Insert(middle, target); \
|
||||
target_list[target] = score; \
|
||||
}; \
|
||||
} while(FALSE)
|
||||
|
||||
|
||||
/// Returns the best target by scoring the distance of each possible target.
|
||||
/// Takes a list to insert the path into, so it can be handed back and re-used.
|
||||
/datum/ai_behavior/proc/get_best_target_by_distance_score(datum/ai_controller/controller, list/targets, set_path = FALSE)
|
||||
if(!length(targets))
|
||||
return null
|
||||
|
||||
var/atom/movable/pawn = controller.pawn
|
||||
var/list/access = controller.get_access()
|
||||
|
||||
var/list/targets_by_score = list()
|
||||
var/list/reachable_targets = list()
|
||||
|
||||
// Sort targets by their estimated score. The last element in the lists has the highest score.
|
||||
while(length(targets))
|
||||
var/index = rand(1, length(targets))
|
||||
var/atom/A = targets[index]
|
||||
targets.Cut(index, index + 1)
|
||||
|
||||
var/score = score_distance(controller, A)
|
||||
|
||||
BINARY_INSERT_TARGET(targets_by_score, A, score)
|
||||
|
||||
// WEE WOO WEE WOO BEHAVIOR-CHANGING MICRO-OPT: we assume turfs further than 1 tile aren't reachable
|
||||
// Because this is true in 99.9999999999999999% of cases
|
||||
if(get_dist(pawn, A) <= 1 && A.Adjacent(pawn))
|
||||
BINARY_INSERT_TARGET(reachable_targets, A, score)
|
||||
|
||||
// Go through our sorted target list until we find a path to one.
|
||||
// Note: This does mean that the found target might not be the ideal one, as it's operating on the estimate
|
||||
// This is a performance thing. We cannot actually use the true best target.
|
||||
var/atom/ideal_atom
|
||||
var/list/ideal_path
|
||||
if(length(reachable_targets))
|
||||
ideal_atom = reachable_targets[length(reachable_targets)]
|
||||
else
|
||||
while(length(targets_by_score))
|
||||
var/atom/candidate = targets_by_score[length(targets_by_score)]
|
||||
targets_by_score.len--
|
||||
|
||||
var/list/path = SSpathfinder.astar_pathfind_now(
|
||||
controller.pawn,
|
||||
candidate,
|
||||
controller.max_target_distance,
|
||||
required_distance,
|
||||
access,
|
||||
HAS_TRAIT(controller.pawn, TRAIT_FLYING),
|
||||
)
|
||||
|
||||
if(path)
|
||||
ideal_atom = candidate
|
||||
ideal_path = path
|
||||
break
|
||||
|
||||
if(set_path && length(ideal_path))
|
||||
controller.clear_blackboard_key(BB_PATH_TO_USE)
|
||||
controller.set_blackboard_key(BB_PATH_TO_USE, ideal_path)
|
||||
return ideal_atom
|
||||
|
||||
#undef BINARY_INSERT_TARGET
|
||||
|
||||
/// Helper for scoring something based on the distance between it and the pawn.
|
||||
/// By default, returns a value between 100 and -INFINITY, where 100 is a distance of 0 steps.
|
||||
/// A distance equal to target_search_radius is zero.
|
||||
/// A distance greater than target_search_radius is negative.
|
||||
/datum/ai_behavior/proc/score_distance(datum/ai_controller/controller, atom/target)
|
||||
var/search_radius = search_radius_override || controller.target_search_radius
|
||||
if(isnull(target))
|
||||
return -INFINITY
|
||||
return 100 * (search_radius - get_dist_manhattan(get_turf(controller.pawn), get_turf(target))) / search_radius
|
||||
|
||||
@@ -45,6 +45,8 @@ RESTRICT_TYPE(/datum/ai_controller)
|
||||
var/continue_processing_when_client = FALSE
|
||||
/// Distance to give up on target.
|
||||
var/max_target_distance = 14
|
||||
/// The search radius when looking for interesting things.
|
||||
var/target_search_radius = 5
|
||||
/// Cooldown for new plans, to prevent AI from going nuts if it can't think of new plans and looping on end
|
||||
COOLDOWN_DECLARE(failed_planning_cooldown)
|
||||
/// All subtrees this AI has available. Will run them in order, so make sure
|
||||
@@ -142,8 +144,9 @@ RESTRICT_TYPE(/datum/ai_controller)
|
||||
return
|
||||
var/list/temp_subtree_list = list()
|
||||
for(var/subtree in planning_subtrees)
|
||||
var/subtree_instance = GLOB.ai_subtrees[subtree]
|
||||
var/datum/ai_planning_subtree/subtree_instance = GLOB.ai_subtrees[subtree]
|
||||
temp_subtree_list += subtree_instance
|
||||
subtree_instance.setup(src)
|
||||
planning_subtrees = temp_subtree_list
|
||||
|
||||
/// Proc to move from one pawn to another. This will destroy the target's existing controller.
|
||||
@@ -344,7 +347,7 @@ RESTRICT_TYPE(/datum/ai_controller)
|
||||
qdel(src)
|
||||
|
||||
/datum/ai_controller/proc/setup_able_to_run()
|
||||
// paused_until is handled by PauseAi() manually
|
||||
// paused_until is handled by pause_ai() manually
|
||||
RegisterSignals(pawn, list(SIGNAL_ADDTRAIT(TRAIT_AI_PAUSED), SIGNAL_REMOVETRAIT(TRAIT_AI_PAUSED)), PROC_REF(update_able_to_run))
|
||||
|
||||
/datum/ai_controller/proc/clear_able_to_run()
|
||||
@@ -521,6 +524,9 @@ RESTRICT_TYPE(/datum/ai_controller)
|
||||
var/list/arguments = args.Copy()
|
||||
arguments[1] = src
|
||||
|
||||
if(!behavior.setup(arglist(arguments)))
|
||||
return FALSE
|
||||
|
||||
// It's still in the plan, don't add it again to current_behaviors
|
||||
// but do keep it in the planned behavior list so its not cancelled
|
||||
if(current_behaviors[behavior])
|
||||
|
||||
@@ -18,3 +18,9 @@
|
||||
/// the other subtrees should multiple exist in the controller.
|
||||
/datum/ai_planning_subtree/proc/select_behaviors(datum/ai_controller/controller, seconds_per_tick)
|
||||
return
|
||||
|
||||
/datum/ai_planning_subtree/proc/setup(datum/ai_controller/controller)
|
||||
return
|
||||
|
||||
/datum/ai_planning_subtree/proc/process_behavior_selection(datum/ai_controller/controller, seconds_per_tick)
|
||||
select_behaviors(controller, seconds_per_tick)
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
/// A simple frustration behavior to be queued in addition to another behavior. If the given duration ellapses and the frustration key is still
|
||||
/// present, the ai will cancel all of it's actions.
|
||||
/datum/ai_behavior/frustration
|
||||
action_cooldown = 0.1 SECONDS
|
||||
|
||||
/datum/ai_behavior/frustration/setup(datum/ai_controller/controller, frustration_key, duration)
|
||||
. = ..()
|
||||
controller.set_blackboard_key(frustration_key, world.time)
|
||||
|
||||
/datum/ai_behavior/frustration/perform(delta_time, datum/ai_controller/controller, frustration_key, duration)
|
||||
..()
|
||||
if(isnull(controller.blackboard[frustration_key]))
|
||||
return AI_BEHAVIOR_SUCCEEDED
|
||||
|
||||
if(world.time >= duration + controller.blackboard[frustration_key])
|
||||
controller.cancel_actions()
|
||||
DEBUG_AI_LOG(controller, "AI got frustrated and cancelled current actions.")
|
||||
return AI_BEHAVIOR_FAILED
|
||||
|
||||
return AI_BEHAVIOR_DELAY
|
||||
|
||||
/datum/ai_behavior/frustration/finish_action(datum/ai_controller/controller, succeeded, frustration_key, duration)
|
||||
. = ..()
|
||||
controller.clear_blackboard_key(frustration_key)
|
||||
@@ -0,0 +1,47 @@
|
||||
/datum/ai_planning_subtree/goap
|
||||
var/list/possible_behaviors = list()
|
||||
|
||||
/datum/ai_planning_subtree/goap/setup(datum/ai_controller/controller, ...)
|
||||
. = ..()
|
||||
controller.set_blackboard_key(BB_PLANNER_BEHAVIORS, list())
|
||||
for(var/behavior_type in possible_behaviors)
|
||||
var/datum/ai_behavior/behavior = GET_AI_BEHAVIOR(behavior_type)
|
||||
if(isnull(behavior))
|
||||
stack_trace("Bad AI behavior: [behavior_type]")
|
||||
continue
|
||||
controller.set_blackboard_key_assoc(BB_PLANNER_BEHAVIORS, behavior, 0)
|
||||
|
||||
/datum/ai_planning_subtree/goap/select_behaviors(datum/ai_controller/controller, seconds_per_tick)
|
||||
. = ..()
|
||||
var/list/behavior_cache = controller.blackboard[BB_PLANNER_BEHAVIORS]
|
||||
var/datum/ai_behavior/candidate = behavior_cache[1]
|
||||
var/score_to_beat = behavior_cache[candidate]
|
||||
|
||||
for(var/behavior in behavior_cache)
|
||||
var/behavior_score = behavior_cache[behavior]
|
||||
if(behavior_score == AI_GOAP_SKIP_BEHAVIOR)
|
||||
continue
|
||||
|
||||
// Filter out lower scoring behaviors
|
||||
if(score_to_beat > behavior_score)
|
||||
continue
|
||||
|
||||
// If there's a tie, pick either.
|
||||
if((behavior_score == score_to_beat) && prob(50))
|
||||
continue
|
||||
|
||||
candidate = behavior
|
||||
score_to_beat = behavior_cache[candidate]
|
||||
|
||||
if(candidate)
|
||||
controller.queue_behavior(candidate.type)
|
||||
return SUBTREE_RETURN_FINISH_PLANNING
|
||||
|
||||
/datum/ai_planning_subtree/goap/process_behavior_selection(datum/ai_controller/controller, seconds_per_tick)
|
||||
for(var/datum/ai_behavior/behavior in controller.blackboard[BB_PLANNER_BEHAVIORS])
|
||||
if(behavior.goap_precondition(controller))
|
||||
controller.set_blackboard_key_assoc(BB_PLANNER_BEHAVIORS, behavior, behavior.goap_weight * behavior.goap_score(controller))
|
||||
else
|
||||
controller.set_blackboard_key_assoc(BB_PLANNER_BEHAVIORS, behavior, AI_GOAP_SKIP_BEHAVIOR)
|
||||
|
||||
return ..()
|
||||
@@ -0,0 +1,44 @@
|
||||
/**
|
||||
* This movement datum represents smart-pathing
|
||||
*/
|
||||
/datum/ai_movement/astar
|
||||
max_pathing_attempts = 20
|
||||
|
||||
/// If FALSE, diagonals will be split into 2 cardinal moves.
|
||||
var/use_diagonals = TRUE
|
||||
|
||||
/datum/ai_movement/astar/start_moving_towards(datum/ai_controller/controller, atom/current_movement_target, min_distance)
|
||||
. = ..()
|
||||
var/atom/movable/moving = controller.pawn
|
||||
var/delay = controller.movement_delay
|
||||
var/max_path_length_override = controller.blackboard[BB_PATH_MAX_LENGTH]
|
||||
|
||||
var/datum/move_loop/has_target/astar/loop = GLOB.move_manager.astar_move(
|
||||
moving,
|
||||
current_movement_target,
|
||||
delay,
|
||||
repath_delay = 0.5 SECONDS,
|
||||
max_path_length = isnull(max_path_length_override) ? AI_MAX_PATH_LENGTH : max_path_length_override,
|
||||
minimum_distance = controller.get_minimum_distance(),
|
||||
access = controller.get_access(),
|
||||
simulated_only = !HAS_TRAIT(controller.pawn, TRAIT_FLYING),
|
||||
subsystem = SSai_movement,
|
||||
extra_info = controller,
|
||||
initial_path = controller.blackboard[BB_PATH_TO_USE],
|
||||
use_diagonals = use_diagonals,
|
||||
)
|
||||
|
||||
controller.clear_blackboard_key(BB_PATH_TO_USE)
|
||||
RegisterSignal(loop, COMSIG_MOVELOOP_PREPROCESS_CHECK, PROC_REF(pre_move))
|
||||
RegisterSignal(loop, COMSIG_MOVELOOP_POSTPROCESS, PROC_REF(post_move))
|
||||
RegisterSignal(loop, COMSIG_MOVELOOP_ASTAR_REPATH, PROC_REF(repath_incoming))
|
||||
|
||||
return loop
|
||||
|
||||
/datum/ai_movement/astar/proc/repath_incoming(datum/move_loop/has_target/astar/source)
|
||||
SIGNAL_HANDLER
|
||||
var/datum/ai_controller/controller = source.extra_info
|
||||
|
||||
source.access = controller.get_access()
|
||||
source.minimum_distance = controller.get_minimum_distance()
|
||||
|
||||
@@ -450,6 +450,16 @@
|
||||
sheet_type = /obj/item/stack/sheet/mineral/iridium
|
||||
ore_type = /obj/item/stack/ore/iridium
|
||||
|
||||
/datum/material/gnesis
|
||||
name = "gnesis"
|
||||
id = MAT_GNESIS
|
||||
sheet_type = /obj/item/stack/sheet/gnesis
|
||||
|
||||
/datum/material/gnesis_glass
|
||||
name = "translucent gnesis"
|
||||
id = MAT_GNESIS_GLASS
|
||||
sheet_type = /obj/item/stack/sheet/gnesis_glass
|
||||
|
||||
/datum/material/bananium
|
||||
name = "Bananium"
|
||||
id = MAT_BANANIUM
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
/datum/keybinding/flock/can_use(client/C, mob/M)
|
||||
return isflockmob(M) && ..()
|
||||
|
||||
/datum/keybinding/flock/intent
|
||||
/// The intent to switch to.
|
||||
var/intent
|
||||
|
||||
/datum/keybinding/flock/intent/down(client/C)
|
||||
. = ..()
|
||||
var/mob/living/basic/flock/M = C.mob
|
||||
M.a_intent_change(intent)
|
||||
|
||||
/datum/keybinding/flock/intent/help
|
||||
name = "Help Intent (Tap)"
|
||||
intent = INTENT_HELP
|
||||
keys = list("1")
|
||||
|
||||
/datum/keybinding/flock/intent/disarm
|
||||
name = "Disarm Intent (Tap)"
|
||||
intent = INTENT_DISARM
|
||||
keys = list("2")
|
||||
|
||||
/datum/keybinding/flock/intent/grab
|
||||
name = "Grab Intent (Tap)"
|
||||
intent = INTENT_GRAB
|
||||
keys = list("3")
|
||||
|
||||
/datum/keybinding/flock/intent/harm
|
||||
name = "Harm Intent (Tap)"
|
||||
intent = INTENT_HARM
|
||||
keys = list("4")
|
||||
|
||||
/datum/keybinding/flock/cycle_part
|
||||
name = "Cycle Part"
|
||||
keys = list("X")
|
||||
|
||||
/datum/keybinding/flock/cycle_part/can_use(client/C, mob/M)
|
||||
return isflockdrone(M)
|
||||
|
||||
/datum/keybinding/flock/cycle_part/down(client/C)
|
||||
. = ..()
|
||||
var/mob/living/basic/flock/drone/M = C.mob
|
||||
if(M.active_part == M.parts[1])
|
||||
M.set_active_part(M.parts[2])
|
||||
else if(M.active_part == M.parts[2])
|
||||
M.set_active_part(M.parts[3])
|
||||
else if(M.active_part == M.parts[3])
|
||||
M.set_active_part(M.parts[1])
|
||||
|
||||
/datum/keybinding/flock/flockphase
|
||||
name = "Toggle Flockphase"
|
||||
keys = list("C")
|
||||
|
||||
/datum/keybinding/flock/flockphase/can_use(client/C, mob/M)
|
||||
return isflockdrone(M)
|
||||
|
||||
/datum/keybinding/flock/flockphase/down(client/C)
|
||||
. = ..()
|
||||
var/mob/living/basic/flock/drone/M = C.mob
|
||||
if(HAS_TRAIT(M, TRAIT_FLOCKPHASE))
|
||||
M.stop_flockphase()
|
||||
else
|
||||
if(M.can_flockphase() && M.flockphase_tax())
|
||||
M.start_flockphase()
|
||||
@@ -6,6 +6,15 @@
|
||||
end_sound = 'sound/machines/shower/shower_end.ogg'
|
||||
volume = 20
|
||||
|
||||
/datum/looping_sound/flock_relay
|
||||
mid_sounds = list('sound/goonstation/flockmind/Flock_Reactor.ogg' = 1)
|
||||
mid_length = 33 SECONDS
|
||||
volume = 40
|
||||
extra_range = 10
|
||||
falloff_exponent = 10
|
||||
falloff_distance = 5
|
||||
channel = CHANNEL_ENGINE
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/datum/looping_sound/supermatter
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
/// Completely generic resource handler
|
||||
/datum/point_holder
|
||||
VAR_PRIVATE/max_points = INFINITY
|
||||
VAR_PRIVATE/points = 0
|
||||
|
||||
/datum/point_holder/proc/has_points(atleast)
|
||||
if(points >= atleast)
|
||||
return points
|
||||
|
||||
/datum/point_holder/proc/is_full()
|
||||
return points == max_points
|
||||
|
||||
/// Returns the number of points needed to fill the holder.
|
||||
/datum/point_holder/proc/how_empty()
|
||||
return max_points - points
|
||||
|
||||
/// Returns the % (0-100) of how full the holder is.
|
||||
/datum/point_holder/proc/percent(step = 1)
|
||||
if(max_points == INFINITY)
|
||||
return 0
|
||||
return round((points / max_points) * 100, step)
|
||||
|
||||
/datum/point_holder/proc/adjust_points(num, check_enough)
|
||||
if(num > 0)
|
||||
return add_points(num)
|
||||
|
||||
else if(num < 0)
|
||||
return remove_points(num, check_enough)
|
||||
|
||||
return 0
|
||||
|
||||
/// Add points. Returns the number of points added.
|
||||
/datum/point_holder/proc/add_points(num)
|
||||
if(points == max_points)
|
||||
return 0
|
||||
|
||||
var/old = points
|
||||
points = min(max_points, points + num)
|
||||
return points - old
|
||||
|
||||
/// Removes points. Returns the number of points removed.
|
||||
/datum/point_holder/proc/remove_points(num, check_enough)
|
||||
if(check_enough && (points < num))
|
||||
return 0
|
||||
|
||||
if(points == 0)
|
||||
return 0
|
||||
|
||||
var/old = points
|
||||
points = max(0, points - num)
|
||||
return old - points
|
||||
|
||||
/datum/point_holder/proc/set_max_points(new_max)
|
||||
max_points = new_max
|
||||
points = min(max_points, points)
|
||||
|
||||
/datum/point_holder/proc/get_max_points()
|
||||
return max_points
|
||||
|
||||
/datum/point_holder/infinite
|
||||
|
||||
/datum/point_holder/infinite/add_points(num)
|
||||
return 0
|
||||
|
||||
/datum/point_holder/infinite/has_points(atleast)
|
||||
return INFINITY
|
||||
|
||||
/datum/point_holder/infinite/remove_points(num, check_enough)
|
||||
return 0
|
||||
|
||||
/datum/point_holder/infinite/set_max_points(new_max)
|
||||
max_points = new_max
|
||||
@@ -15,6 +15,10 @@
|
||||
/// Can be used in certain situations where you may have effects that trigger only at the edge,
|
||||
/// while also wanting the field effect to trigger at edge turfs as well
|
||||
var/edge_is_a_field = FALSE
|
||||
|
||||
/// If TRUE, view() will be used over range(). If this is used, edge turfs will not exist!
|
||||
var/use_view = FALSE
|
||||
|
||||
/// All turfs on the inside of the proximity monitor - range - 1 turfs
|
||||
var/list/turf/field_turfs = list()
|
||||
/// All turfs on the very last tile of the proximity monitor's radius
|
||||
@@ -131,6 +135,13 @@
|
||||
/datum/proximity_monitor/advanced/proc/update_new_turfs()
|
||||
if(ignore_if_not_on_turf && !isturf(host.loc))
|
||||
return list(FIELD_TURFS_KEY = list(), EDGE_TURFS_KEY = list())
|
||||
|
||||
if(use_view)
|
||||
var/list/turfs = list()
|
||||
for(var/turf/T as turf in view(current_range, host))
|
||||
turfs += T
|
||||
return list(FIELD_TURFS_KEY = turfs, EDGE_TURFS_KEY = list())
|
||||
|
||||
var/list/local_field_turfs = list()
|
||||
var/list/local_edge_turfs = list()
|
||||
var/turf/center = get_turf(host)
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
/datum/proximity_monitor/advanced/interceptor
|
||||
use_view = TRUE
|
||||
|
||||
/datum/proximity_monitor/advanced/interceptor/New(atom/_host, range, _ignore_if_not_on_turf)
|
||||
..()
|
||||
recalculate_field(TRUE)
|
||||
|
||||
/datum/proximity_monitor/advanced/interceptor/Destroy()
|
||||
return ..()
|
||||
|
||||
/datum/proximity_monitor/advanced/interceptor/field_turf_crossed(atom/movable/movable, turf/old_location, turf/new_location)
|
||||
. = ..()
|
||||
if(isprojectile(movable))
|
||||
astype(host, /obj/structure/flock/interceptor).try_intercept_projectile(movable)
|
||||
Reference in New Issue
Block a user