From 6feaf1a218c1a503a243bb9497bb69cc190f43b2 Mon Sep 17 00:00:00 2001 From: Lohikar Date: Sun, 24 Dec 2017 14:24:55 -0600 Subject: [PATCH] Mob AI SS & Misc. Mob Cleanup (#3990) --- aurorastation.dme | 1 + code/__defines/subsystem-defines.dm | 5 + code/_helpers/lists.dm | 6 + code/controllers/subsystems/mob.dm | 2 +- code/controllers/subsystems/mob_ai.dm | 66 ++++ code/controllers/subsystems/statistics.dm | 13 +- code/modules/mob/dead/observer/observer.dm | 2 + code/modules/mob/dview.dm | 1 + code/modules/mob/living/bot/cleanbot.dm | 20 +- code/modules/mob/living/bot/farmbot.dm | 7 +- code/modules/mob/living/bot/floorbot.dm | 28 +- code/modules/mob/living/bot/medbot.dm | 67 ++-- code/modules/mob/living/bot/secbot.dm | 62 ++-- .../mob/living/carbon/alien/diona/life.dm | 9 +- .../mob/living/carbon/human/human_species.dm | 3 + code/modules/mob/living/carbon/human/life.dm | 7 +- .../modules/mob/living/carbon/metroid/life.dm | 12 +- code/modules/mob/living/silicon/silicon.dm | 4 +- code/modules/mob/living/simple_animal/bees.dm | 285 +++++++++--------- .../simple_animal/constructs/constructs.dm | 115 +++---- .../mob/living/simple_animal/friendly/cat.dm | 18 +- .../living/simple_animal/friendly/corgi.dm | 31 +- .../mob/living/simple_animal/friendly/crab.dm | 2 +- .../simple_animal/friendly/farm_animals.dm | 31 +- .../living/simple_animal/friendly/lizard.dm | 2 +- .../living/simple_animal/friendly/mouse.dm | 12 +- .../mob/living/simple_animal/hostile/bear.dm | 12 +- .../hostile/commanded/commanded.dm | 25 +- .../simple_animal/hostile/giant_spider.dm | 10 +- .../living/simple_animal/hostile/hostile.dm | 34 +-- .../mob/living/simple_animal/parrot.dm | 3 +- .../modules/mob/living/simple_animal/shade.dm | 17 +- .../mob/living/simple_animal/simple_animal.dm | 188 +++++------- code/modules/mob/login.dm | 11 +- code/modules/mob/logout.dm | 7 +- code/modules/mob/mob.dm | 9 +- code/modules/mob/mob_defines.dm | 2 + code/modules/spells/spells.dm | 2 +- code/world.dm | 5 +- 39 files changed, 589 insertions(+), 547 deletions(-) create mode 100644 code/controllers/subsystems/mob_ai.dm diff --git a/aurorastation.dme b/aurorastation.dme index 4aa198007ed..31f9ed09f43 100644 --- a/aurorastation.dme +++ b/aurorastation.dme @@ -167,6 +167,7 @@ #include "code\controllers\subsystems\lighting.dm" #include "code\controllers\subsystems\machinery.dm" #include "code\controllers\subsystems\mob.dm" +#include "code\controllers\subsystems\mob_ai.dm" #include "code\controllers\subsystems\night_lighting.dm" #include "code\controllers\subsystems\openturf.dm" #include "code\controllers\subsystems\orbit.dm" diff --git a/code/__defines/subsystem-defines.dm b/code/__defines/subsystem-defines.dm index d7b72ed06ac..3f7ade85b33 100644 --- a/code/__defines/subsystem-defines.dm +++ b/code/__defines/subsystem-defines.dm @@ -90,3 +90,8 @@ // -- SSicon_cache -- #define LIGHT_FIXTURE_CACHE(icon,state,color) SSicon_cache.light_fixture_cache["[icon]_[state]_[color]"] || (SSicon_cache.light_fixture_cache["[icon]_[state]_[color]"] = SSicon_cache.generate_color_variant(icon,state,color)) + + +// -- SSmob_ai -- +#define MOB_START_THINKING(mob) if (!mob.thinking_enabled) { SSmob_ai.processing += mob; mob.on_think_enabled(); mob.thinking_enabled = TRUE; } +#define MOB_STOP_THINKING(mob) SSmob_ai.processing -= mob; mob.on_think_disabled(); mob.thinking_enabled = FALSE; diff --git a/code/_helpers/lists.dm b/code/_helpers/lists.dm index f9fb8680235..12c75b33e31 100644 --- a/code/_helpers/lists.dm +++ b/code/_helpers/lists.dm @@ -318,6 +318,12 @@ if(typecache_include[A.type] && !typecache_exclude[A.type]) . += A +/proc/range_in_typecache(dist, center, list/typecache) + for (var/thing in range(dist, center)) + var/atom/A = thing + if (typecache[A.type]) + return TRUE + /proc/typecache_first_match(list/target, list/typecache) for (var/thing in target) var/datum/D = thing diff --git a/code/controllers/subsystems/mob.dm b/code/controllers/subsystems/mob.dm index 53d99ad16d7..5110482edfe 100644 --- a/code/controllers/subsystems/mob.dm +++ b/code/controllers/subsystems/mob.dm @@ -1,7 +1,7 @@ /var/datum/controller/subsystem/mobs/SSmob /datum/controller/subsystem/mobs - name = "Mobs" + name = "Mobs - Life" flags = SS_NO_INIT priority = SS_PRIORITY_MOB diff --git a/code/controllers/subsystems/mob_ai.dm b/code/controllers/subsystems/mob_ai.dm new file mode 100644 index 00000000000..d381ff63668 --- /dev/null +++ b/code/controllers/subsystems/mob_ai.dm @@ -0,0 +1,66 @@ +/var/datum/controller/subsystem/mob_ai/SSmob_ai + +/datum/controller/subsystem/mob_ai + name = "Mobs - AI" + flags = SS_NO_INIT + priority = SS_PRIORITY_MOB + + var/list/processing = list() + var/list/currentrun = list() + var/list/slept = list() + +/datum/controller/subsystem/mob_ai/New() + NEW_SS_GLOBAL(SSmob_ai) + +/datum/controller/subsystem/mob_ai/stat_entry() + ..("P:[processing.len]") + +/datum/controller/subsystem/mob_ai/fire(resumed = FALSE) + if (!resumed) + src.currentrun = processing.Copy() + + var/list/currentrun = src.currentrun + while (currentrun.len) + var/mob/M = currentrun[currentrun.len] + currentrun.len-- + + if (QDELETED(M)) + processing -= M + + if (MC_TICK_CHECK) + return + continue + + if (M.ckey) + // cliented mobs are not allowed to think + log_debug("SSmob_ai: Type '[M.type]' was still thinking despite having a client!") + MOB_STOP_THINKING(M) + + if (MC_TICK_CHECK) + return + continue + + var/time = world.time + + if (!M.frozen && !M.stat) + M.think() + + if (time != world.time && !slept[M.type]) + slept[M.type] = TRUE + var/diff = world.time - time + log_debug("SSmob_ai: Type '[M.type]' slept for [diff] ds in think()! Suppressing further warnings.") + + if (MC_TICK_CHECK) + return + +/mob + var/tmp/thinking_enabled = FALSE + +/mob/proc/think() + return + +/mob/proc/on_think_disabled() + walk_to(src, 0) + +/mob/proc/on_think_enabled() + return diff --git a/code/controllers/subsystems/statistics.dm b/code/controllers/subsystems/statistics.dm index d70a4885321..290a24e0947 100644 --- a/code/controllers/subsystems/statistics.dm +++ b/code/controllers/subsystems/statistics.dm @@ -25,13 +25,12 @@ var/list/datum/feedback_variable/feedback = list() + var/status_needs_update = FALSE + /datum/controller/subsystem/statistics/New() NEW_SS_GLOBAL(SSfeedback) /datum/controller/subsystem/statistics/Initialize(timeofday) - if (!config.kick_inactive && !(config.sql_enabled && config.sql_stats)) - can_fire = FALSE - for (var/type in subtypesof(/datum/statistic) - list(/datum/statistic/numeric, /datum/statistic/grouped)) var/datum/statistic/S = new type if (!S.name) @@ -70,6 +69,14 @@ var/err = query.ErrorMsg() log_game("SQL ERROR during population polling. Error : \[[err]\]\n") + if (status_needs_update) + // Update world status. + world.update_status() + status_needs_update = FALSE + +/datum/controller/subsystem/statistics/proc/update_status() + status_needs_update = TRUE + /datum/controller/subsystem/statistics/Recover() src.messages = SSfeedback.messages src.messages_admin = SSfeedback.messages_admin diff --git a/code/modules/mob/dead/observer/observer.dm b/code/modules/mob/dead/observer/observer.dm index 0f3f46c77da..c4d50061e9c 100644 --- a/code/modules/mob/dead/observer/observer.dm +++ b/code/modules/mob/dead/observer/observer.dm @@ -38,6 +38,8 @@ var/global/list/image/ghost_sightless_images = list() //this is a list of images var/obj/item/device/multitool/ghost_multitool incorporeal_move = 1 + mob_thinks = FALSE + /mob/dead/observer/New(mob/body) if (istype(body, /mob/dead/observer)) return//A ghost can't become a ghost. diff --git a/code/modules/mob/dview.dm b/code/modules/mob/dview.dm index d65aa461a57..33c677e2b48 100644 --- a/code/modules/mob/dview.dm +++ b/code/modules/mob/dview.dm @@ -11,6 +11,7 @@ var/mob/dview/dview_mob = new simulated = 0 see_in_dark = 1e6 + mob_thinks = FALSE /mob/dview/New() ..() diff --git a/code/modules/mob/living/bot/cleanbot.dm b/code/modules/mob/living/bot/cleanbot.dm index 423def2c956..5ecae44b822 100644 --- a/code/modules/mob/living/bot/cleanbot.dm +++ b/code/modules/mob/living/bot/cleanbot.dm @@ -90,9 +90,11 @@ var/list/cleanbot_types // Going to use this to generate a list of types once th return 1 return +/mob/living/bot/cleanbot/proc/remove_from_ignore(path) + ignorelist -= path + /mob/living/bot/cleanbot/Life() ..() - if(!on) ignorelist = list() return @@ -117,16 +119,21 @@ var/list/cleanbot_types // Going to use this to generate a list of types once th visible_message("Something flies out of [src]. He seems to be acting oddly.") var/obj/effect/decal/cleanable/blood/gibs/gib = new /obj/effect/decal/cleanable/blood/gibs(loc) ignorelist += gib - spawn(600) - ignorelist -= gib + addtimer(CALLBACK(src, .proc/remove_from_ignore, gib), 600) +/mob/living/bot/cleanbot/think() + ..() + if (!on) + return if(pulledby) // Don't wiggle if someone pulls you - patrol_path = list() + patrol_path.Cut() return var/found_spot - if(!should_patrol) return + if(!should_patrol) + return + // This loop will progressively search outwards for /cleanables in view(), gradually to prevent excessively large view() calls when none are needed. search_for: // We use the label so we can break out of this loop from within the next loop. // Not breaking out of these loops properly is where the infinite loop was coming from before. @@ -154,7 +161,6 @@ var/list/cleanbot_types // Going to use this to generate a list of types once th D.clean_marked = null - if(!found_spot && !target) // No targets in range if(!patrol_path || !patrol_path.len) if(!signal_sent || signal_sent > world.time + 200) // Waited enough or didn't send yet @@ -188,8 +194,6 @@ var/list/cleanbot_types // Going to use this to generate a list of types once th if(moved) patrol_path -= patrol_path[1] - - /mob/living/bot/cleanbot/UnarmedAttack(var/obj/effect/decal/cleanable/D, var/proximity) if(!..()) return diff --git a/code/modules/mob/living/bot/farmbot.dm b/code/modules/mob/living/bot/farmbot.dm index 6120398a646..c434b05c989 100644 --- a/code/modules/mob/living/bot/farmbot.dm +++ b/code/modules/mob/living/bot/farmbot.dm @@ -125,11 +125,12 @@ /mob/living/bot/farmbot/Life() ..() - if(!on) - return if(emagged && prob(1)) flick("farmbot_broke", src) - if(client) + +/mob/living/bot/farmbot/think() + ..() + if(!on) return if(target) diff --git a/code/modules/mob/living/bot/floorbot.dm b/code/modules/mob/living/bot/floorbot.dm index 6fdc875f9fd..127a1066dec 100644 --- a/code/modules/mob/living/bot/floorbot.dm +++ b/code/modules/mob/living/bot/floorbot.dm @@ -106,7 +106,6 @@ /mob/living/bot/floorbot/Life() ..() - if(!on) return @@ -115,17 +114,13 @@ tilemake = 0 addTiles(1) - if(client) - return - if(prob(5)) custom_emote(2, "makes an excited booping beeping sound!") - if(ignorelist.len) // Don't stick forever - for(var/T in ignorelist) - if(prob(1)) - ignorelist -= T - +/mob/living/bot/floorbot/think() + ..() + if (!on) + return if(amount && !emagged) if(!target && targetdirection) // Building a bridge var/turf/T = get_step(src, targetdirection) @@ -137,7 +132,7 @@ if(!target) // Fixing floors for(var/turf/T in view(src)) - if(T.loc.name == "Space") + if (istype(T.loc, /area/space)) continue if(T in ignorelist) continue @@ -151,7 +146,7 @@ if(emagged) // Time to griff for(var/turf/simulated/floor/D in view(src)) - if(D.loc.name == "Space") + if (istype(D.loc, /area/space)) continue if(D in ignorelist) continue @@ -187,6 +182,17 @@ step_to(src, path[1]) path -= path[1] + if(ignorelist.len) // Don't stick forever + for(var/T in ignorelist) + if(prob(1)) + ignorelist -= T + +/mob/living/bot/floorbot/on_think_disabled() + ..() + ignorelist.Cut() + path.Cut() + target = null + /mob/living/bot/floorbot/UnarmedAttack(var/atom/A, var/proximity) if(!..()) return diff --git a/code/modules/mob/living/bot/medbot.dm b/code/modules/mob/living/bot/medbot.dm index 86a9895dd8c..47f2798aaf1 100644 --- a/code/modules/mob/living/bot/medbot.dm +++ b/code/modules/mob/living/bot/medbot.dm @@ -34,47 +34,44 @@ QDEL_NULL(reagent_glass) return ..() -/mob/living/bot/medbot/Life() +/mob/living/bot/medbot/think() ..() - if(!on) return - if(!client) + if(vocal && prob(1)) + var/message = pick("Radar, put a mask on!", "There's always a catch, and it's the best there is.", "I knew it, I should've been a plastic surgeon.", "What kind of medbay is this? Everyone's dropping like dead flies.", "Delicious!") + say(message) - if(vocal && prob(1)) - var/message = pick("Radar, put a mask on!", "There's always a catch, and it's the best there is.", "I knew it, I should've been a plastic surgeon.", "What kind of medbay is this? Everyone's dropping like dead flies.", "Delicious!") - say(message) - - if(patient) - if(Adjacent(patient)) - if(!currently_healing) - INVOKE_ASYNC(src, .proc/UnarmedAttack, patient) - else - if(path.len && (get_dist(patient, path[path.len]) > 2)) // We have a path, but it's off - path = list() - if(!path.len && (get_dist(src, patient) > 1)) - spawn(0) - path = AStar(loc, get_turf(patient), /turf/proc/CardinalTurfsWithAccess, /turf/proc/Distance, 0, 30, id = botcard) - if(!path) - path = list() - if(path.len) - step_to(src, path[1]) - path -= path[1] - ++frustration - if(get_dist(src, patient) > 7 || frustration > 8) - patient = null + if(patient) + if(Adjacent(patient)) + if(!currently_healing) + INVOKE_ASYNC(src, .proc/UnarmedAttack, patient) else - for(var/mob/living/carbon/human/H in view(7, src)) // Time to find a patient! - if(valid_healing_target(H)) - patient = H - frustration = 0 - if(last_newpatient_speak + 300 < world.time) - var/message = pick("Hey, [H.name]! Hold on, I'm coming.", "Wait [H.name]! I want to help!", "[H.name], you appear to be injured!") - say(message) - custom_emote(1, "points at [H.name].") - last_newpatient_speak = world.time - break + if(path.len && (get_dist(patient, path[path.len]) > 2)) // We have a path, but it's off + path = list() + if(!path.len && (get_dist(src, patient) > 1)) + spawn(0) + path = AStar(loc, get_turf(patient), /turf/proc/CardinalTurfsWithAccess, /turf/proc/Distance, 0, 30, id = botcard) + if(!path) + path = list() + if(path.len) + step_to(src, path[1]) + path -= path[1] + ++frustration + if(get_dist(src, patient) > 7 || frustration > 8) + patient = null + else + for(var/mob/living/carbon/human/H in view(7, src)) // Time to find a patient! + if(valid_healing_target(H)) + patient = H + frustration = 0 + if(last_newpatient_speak + 300 < world.time) + var/message = pick("Hey, [H.name]! Hold on, I'm coming.", "Wait [H.name]! I want to help!", "[H.name], you appear to be injured!") + say(message) + custom_emote(1, "points at [H.name].") + last_newpatient_speak = world.time + break /mob/living/bot/medbot/UnarmedAttack(var/mob/living/carbon/human/H, var/proximity) if(!..()) diff --git a/code/modules/mob/living/bot/secbot.dm b/code/modules/mob/living/bot/secbot.dm index b74d1b5a5d6..7837078f06f 100644 --- a/code/modules/mob/living/bot/secbot.dm +++ b/code/modules/mob/living/bot/secbot.dm @@ -40,8 +40,21 @@ var/nearest_beacon // Tag of the beakon that we assume to be the closest one var/bot_version = 1.4 - var/list/threat_found_sounds = new('sound/voice/bcriminal.ogg', 'sound/voice/bjustice.ogg', 'sound/voice/bfreeze.ogg') - var/list/preparing_arrest_sounds = new('sound/voice/bgod.ogg', 'sound/voice/biamthelaw.ogg', 'sound/voice/bsecureday.ogg', 'sound/voice/bradio.ogg', 'sound/voice/binsult.ogg', 'sound/voice/bcreep.ogg') + var/list/threat_found_sounds = list( + 'sound/voice/bcriminal.ogg', + 'sound/voice/bjustice.ogg', + 'sound/voice/bfreeze.ogg' + ) + var/list/preparing_arrest_sounds = list( + 'sound/voice/bgod.ogg', + 'sound/voice/biamthelaw.ogg', + 'sound/voice/bsecureday.ogg', + 'sound/voice/bradio.ogg', + 'sound/voice/binsult.ogg', + 'sound/voice/bcreep.ogg' + ) + + var/datum/callback/patrol_callback // this is here so we don't constantly recreate this datum, it being identical each time. /mob/living/bot/secbot/beepsky name = "Officer Beepsky" @@ -56,6 +69,9 @@ if(SSradio) SSradio.add_object(listener, control_freq, filter = RADIO_SECBOT) SSradio.add_object(listener, beacon_freq, filter = RADIO_NAVBEACONS) + + if (!patrol_callback) + patrol_callback = CALLBACK(src, .proc/patrol_step) /mob/living/bot/secbot/Destroy() QDEL_NULL(listener) @@ -145,14 +161,12 @@ awaiting_surrender = 5 mode = SECBOT_HUNT -/mob/living/bot/secbot/Life() +/mob/living/bot/secbot/think() ..() if(!on) return - if(client) - return - if(!target) + if(QDELETED(target)) scan_view() if(!locked && (mode == SECBOT_START_PATROL || mode == SECBOT_PATROL)) // Stop running away when we set you up @@ -189,12 +203,10 @@ RangedAttack(target) else step_towards(src, target) // Melee bots chase a bit faster - spawn(8) - if(!Adjacent(target)) - step_towards(src, target) - spawn(16) - if(!Adjacent(target)) - step_towards(src, target) + + var/cb = CALLBACK(src, .proc/step_nonadjacent, target) + addtimer(cb, 8) + addtimer(cb, 16) if(SECBOT_ARREST) // Target is next to us - attack it if(!target) @@ -253,18 +265,19 @@ if(SECBOT_PATROL) patrol_step() - spawn(10) - patrol_step() + addtimer(patrol_callback, 10) return if(SECBOT_SUMMON) patrol_step() - spawn(8) - patrol_step() - spawn(16) - patrol_step() + addtimer(patrol_callback, 8) + addtimer(patrol_callback, 16) return +/mob/living/bot/secbot/proc/step_nonadjacent(target) + if (!Adjacent(target)) + step_towards(src, target) + /mob/living/bot/secbot/UnarmedAttack(var/mob/M, var/proximity) if(!..()) return @@ -287,9 +300,7 @@ do_attack_animation(C) is_attacking = 1 update_icons() - spawn(2) - is_attacking = 0 - update_icons() + addtimer(CALLBACK(src, .proc/stop_attacking_cb), 2) visible_message("[C] was prodded by [src] with a stun baton!") else playsound(loc, 'sound/weapons/handcuffs.ogg', 30, 1, -2) @@ -308,11 +319,13 @@ playsound(loc, "swing_hit", 50, 1, -1) is_attacking = 1 update_icons() - spawn(2) - is_attacking = 0 - update_icons() + addtimer(CALLBACK(src, .proc/stop_attacking_cb), 2) visible_message("[M] was beaten by [src] with a stun baton!") +/mob/living/bot/secbot/proc/stop_attacking_cb() + is_attacking = FALSE + update_icons() + /mob/living/bot/secbot/explode() visible_message("[src] blows apart!") var/turf/Tsec = get_turf(src) @@ -332,6 +345,7 @@ qdel(src) /mob/living/bot/secbot/proc/scan_view() + target = null for(var/mob/living/M in view(7, src)) if(M.invisibility >= INVISIBILITY_LEVEL_ONE) continue diff --git a/code/modules/mob/living/carbon/alien/diona/life.dm b/code/modules/mob/living/carbon/alien/diona/life.dm index 4a7ec297929..9cae23c109d 100644 --- a/code/modules/mob/living/carbon/alien/diona/life.dm +++ b/code/modules/mob/living/carbon/alien/diona/life.dm @@ -33,12 +33,13 @@ updatehealth() check_status_as_organ() + if (!gestalt) + ..() + +/mob/living/carbon/alien/diona/think() + ..() if (!gestalt) if(master_nymph && !client && master_nymph != src) walk_to(src,master_nymph,1,movement_delay()) else walk_to(src,0) - ..() - - - diff --git a/code/modules/mob/living/carbon/human/human_species.dm b/code/modules/mob/living/carbon/human/human_species.dm index 4ae5d4bb40a..42af6f52cf0 100644 --- a/code/modules/mob/living/carbon/human/human_species.dm +++ b/code/modules/mob/living/carbon/human/human_species.dm @@ -2,6 +2,9 @@ real_name = "Test Dummy" status_flags = GODMODE|CANPUSH +/mob/living/carbon/human/dummy/mannequin + mob_thinks = FALSE + INITIALIZE_IMMEDIATE(/mob/living/carbon/human/dummy/mannequin) /mob/living/carbon/human/dummy/mannequin/Initialize() diff --git a/code/modules/mob/living/carbon/human/life.dm b/code/modules/mob/living/carbon/human/life.dm index fd28ae6b76e..8f8adfe11fb 100644 --- a/code/modules/mob/living/carbon/human/life.dm +++ b/code/modules/mob/living/carbon/human/life.dm @@ -85,9 +85,6 @@ if (is_diona()) diona_handle_light(DS) - if(!client) - species.handle_npc(src) - handle_stasis_bag() if(!handle_some_updates()) @@ -101,6 +98,10 @@ if(mind && mind.vampire) handle_vampire() +/mob/living/carbon/human/think() + ..() + species.handle_npc(src) + /mob/living/carbon/human/proc/handle_some_updates() if(life_tick > 5 && timeofdeath && (timeofdeath < 5 || world.time - timeofdeath > 6000)) //We are long dead, or we're junk mobs spawned like the clowns on the clown shuttle return 0 diff --git a/code/modules/mob/living/carbon/metroid/life.dm b/code/modules/mob/living/carbon/metroid/life.dm index d4ebc5ff131..81e72c40332 100644 --- a/code/modules/mob/living/carbon/metroid/life.dm +++ b/code/modules/mob/living/carbon/metroid/life.dm @@ -12,12 +12,12 @@ if(is_ventcrawling == 0) // Stops sight returning to normal if inside a vent sight = initial(sight) - if (!client) - handle_targets() - if (!AIproc) - spawn() - handle_AI() - handle_speech_and_mood() +/mob/living/carbon/slime/think() + ..() + handle_targets() + if (!AIproc) + handle_AI() + handle_speech_and_mood() /mob/living/carbon/slime/handle_environment(datum/gas_mixture/environment) if(!environment) diff --git a/code/modules/mob/living/silicon/silicon.dm b/code/modules/mob/living/silicon/silicon.dm index de262786def..fe97d67bcd7 100644 --- a/code/modules/mob/living/silicon/silicon.dm +++ b/code/modules/mob/living/silicon/silicon.dm @@ -29,6 +29,8 @@ var/obj/item/weapon/card/id/idcard var/idcard_type = /obj/item/weapon/card/id/synthetic + mob_thinks = FALSE + #define SEC_HUD 1 //Security HUD mode #define MED_HUD 2 //Medical HUD mode @@ -384,4 +386,4 @@ if (!underdoor) spawn(3)//A slight delay to let us finish walking out from under the door - layer = initial(layer) \ No newline at end of file + layer = initial(layer) diff --git a/code/modules/mob/living/simple_animal/bees.dm b/code/modules/mob/living/simple_animal/bees.dm index b2a8abe0c77..e8b04f296cd 100644 --- a/code/modules/mob/living/simple_animal/bees.dm +++ b/code/modules/mob/living/simple_animal/bees.dm @@ -67,163 +67,155 @@ ..() - if(stat == CONSCIOUS) - //if we're strong enough, sting some people - var/mob/living/carbon/human/M = target_mob - var/sting_prob = 40 // Bees will always try to sting. - var/prob_mult = 1 - if(M && M in view(src,1)) // Can I see my target? - var/obj/item/clothing/worn_suit = M.wear_suit - var/obj/item/clothing/worn_helmet = M.head - if(worn_suit) // Are you wearing clothes? - if ((worn_suit.flags & THICKMATERIAL)) - prob_mult -= 0.7 - else - prob_mult -= 0.01 * (min(LAZYACCESS(worn_suit.armor, "bio"), 70)) // Is it sealed? I can't get to 70% of your body. - if(worn_helmet) - if ((worn_helmet.flags & THICKMATERIAL)) - prob_mult -= 0.3 - else - prob_mult -= 0.01 *(min(LAZYACCESS(worn_helmet.armor, "bio"), 30))// Is your helmet sealed? I can't get to 30% of your body. - if( prob(sting_prob*prob_mult) && (M.stat == CONSCIOUS || (M.stat == UNCONSCIOUS && prob(25*prob_mult))) ) // Try to sting! If you're not moving, think about stinging. - M.apply_damage(min(strength*0.85,2)+mut, BURN, sharp=1) // Stinging. The more mutated I am, the harder I sting. - M.apply_damage(max(strength*0.2,(round(feral/10,1)*(max((round(strength/20,1)),1)))+toxic), TOX) // Bee venom based on how angry I am and how many there are of me! - M << "You have been stung!" - M.flash_pain() + +/mob/living/simple_animal/bee/think() + ..() + if (stat != CONSCIOUS) + return + + //if we're strong enough, sting some people + var/mob/living/carbon/human/M = target_mob + var/sting_prob = 40 // Bees will always try to sting. + var/prob_mult = 1 + if(M && M in view(src,1)) // Can I see my target? + var/obj/item/clothing/worn_suit = M.wear_suit + var/obj/item/clothing/worn_helmet = M.head + if(worn_suit) // Are you wearing clothes? + if ((worn_suit.flags & THICKMATERIAL)) + prob_mult -= 0.7 + else + prob_mult -= 0.01 * (min(LAZYACCESS(worn_suit.armor, "bio"), 70)) // Is it sealed? I can't get to 70% of your body. + if(worn_helmet) + if ((worn_helmet.flags & THICKMATERIAL)) + prob_mult -= 0.3 + else + prob_mult -= 0.01 *(min(LAZYACCESS(worn_helmet.armor, "bio"), 30))// Is your helmet sealed? I can't get to 30% of your body. + if( prob(sting_prob*prob_mult) && (M.stat == CONSCIOUS || (M.stat == UNCONSCIOUS && prob(25*prob_mult))) ) // Try to sting! If you're not moving, think about stinging. + M.apply_damage(min(strength*0.85,2)+mut, BURN, sharp=1) // Stinging. The more mutated I am, the harder I sting. + M.apply_damage(max(strength*0.2,(round(feral/10,1)*(max((round(strength/20,1)),1)))+toxic), TOX) // Bee venom based on how angry I am and how many there are of me! + M << "You have been stung!" + M.flash_pain() - //calm down a little bit - if(feral > 0 && !target_mob) - if(prob(feral * 20)) - feral -= 1 - else - //if feral is less than 0, we're becalmed by smoke or steam - if(feral < 0) - feral += 1 + //calm down a little bit + if(feral > 0 && !target_mob) + if(prob(feral * 20)) + feral -= 1 + else + //if feral is less than 0, we're becalmed by smoke or steam + if(feral < 0) + feral += 1 + + if(target_mob) + target_mob = null + target_turf = null + if(strength > 5) + //calm down and spread out a little + var/mob/living/simple_animal/bee/B = new(get_turf(src)) + B.strength = rand(1,5) + src.strength -= B.strength + update_icons() + B.update_icons() + if(src.parent) + B.parent = src.parent + src.parent.owned_bee_swarms.Add(B) + + //make some noise + if(prob(3)) + src.visible_message("[pick("Buzzzz.","Hmmmmm.","Bzzz.")]") + playsound(src.loc, pick('sound/effects/Buzz1.ogg','sound/effects/Buzz2.ogg'), 15, 1,-4) + + if (feral && isturf(loc)) + //smoke, water and steam calms us down + var/static/list/calmers = typecacheof(list( + /obj/effect/effect/smoke, + /obj/effect/effect/water, + /obj/effect/effect/foam, + /obj/effect/effect/steam, + /obj/effect/mist + )) + + if(range_in_typecache(src, 2, calmers)) + if(feral > 0) + src.visible_message("The bees calm down!") + feral = -15 + target_mob = null + target_turf = null + wander = 1 + + for(var/mob/living/simple_animal/bee/B in src.loc) + if(B == src) + continue + + if(feral > 0) + src.strength += B.strength + B.strength = 0 + B.update_icons() + update_icons() + + else if(prob(10)) + //make the other swarm of bees stronger, then move away + var/total_bees = B.strength + src.strength + if(total_bees < 10) + B.strength = min(5, total_bees) + src.strength = total_bees - B.strength - if(target_mob) - target_mob = null - target_turf = null - if(strength > 5) - //calm down and spread out a little - var/mob/living/simple_animal/bee/B = new(get_turf(src)) - B.strength = rand(1,5) - src.strength -= B.strength update_icons() B.update_icons() - if(src.parent) - B.parent = src.parent - src.parent.owned_bee_swarms.Add(B) - - //make some noise - if(prob(3)) - src.visible_message("[pick("Buzzzz.","Hmmmmm.","Bzzz.")]") - playsound(src.loc, pick('sound/effects/Buzz1.ogg','sound/effects/Buzz2.ogg'), 15, 1,-4) - - var/calming = 0 - if (feral && istype(loc, /turf)) - //smoke, water and steam calms us down - - var/list/calmers = list(/obj/effect/effect/smoke, \ - /obj/effect/effect/water, \ - /obj/effect/effect/foam, \ - /obj/effect/effect/steam, \ - /obj/effect/mist) - - for(var/obj/effect/E in range(src, 2))//Check in 1-tile range because these effects often have 3x3 sprites - for (var/e in calmers) - if(istype(E, e)) - calming = 1 - break - if (calming) - break - - if(calming) - if(feral > 0) - src.visible_message("The bees calm down!") - feral = -15 - target_mob = null - target_turf = null - wander = 1 - - for(var/mob/living/simple_animal/bee/B in src.loc) - if(B == src) - continue - - if(feral > 0) - - src.strength += B.strength - B.strength = 0 - B.update_icons() - update_icons() - - else if(prob(10)) - //make the other swarm of bees stronger, then move away - var/total_bees = B.strength + src.strength - if(total_bees < 10) - B.strength = min(5, total_bees) - src.strength = total_bees - B.strength - - update_icons() - B.update_icons() - if(src.strength <= 0) - qdel(src) - return - var/turf/simulated/floor/T = get_turf(get_step(src, pick(1,2,4,8))) - density = 1 - if(T.Enter(src, get_turf(src))) - src.loc = T - density = 0 - break + if(src.strength <= 0) + qdel(src) + return + var/turf/simulated/floor/T = get_step(src, pick(1,2,4,8)) + density = 1 + Move(T) + density = 0 + break - if(target_mob)//If we have a target - if(target_mob in view(src,7))//Check that we can still see them - target_turf = get_turf(target_mob)//If so, update the location - wander = 0 - else//Otherwise, if our target is out of view, we clear them so we can pick a new one in the next step - target_mob = null - target_turf = null - wander = 1 + if(target_mob)//If we have a target + if(target_mob in view(src,7))//Check that we can still see them + target_turf = get_turf(target_mob)//If so, update the location + wander = 0 + else//Otherwise, if our target is out of view, we clear them so we can pick a new one in the next step + target_mob = null + target_turf = null + wander = 1 - //If we're angry but have no target, lets search for one - if (!target_mob && feral) - for(var/mob/living/carbon/G in view(src,7)) - target_mob = G - break + //If we're angry but have no target, lets search for one + if (!target_mob && feral) + for(var/mob/living/carbon/G in view(src,7)) + target_mob = G + break - //if we're chasing someone, get a little bit angry - if(target_mob && prob(5)) - feral++ - - if(target_turf) - if (!(DirBlocked(get_step(src, get_dir(src,target_turf)),get_dir(src,target_turf)))) // Check for windows and doors! - Move(get_step(src, get_dir(src,target_turf))) - if (prob(10)) - src.visible_message("The bees swarm after [target_mob]!") - if(src.loc == target_turf) - target_turf = null - wander = 1 - else - //find some flowers, harvest - //angry bee swarms don't hang around - if(feral > 0) - turns_per_move = rand(1,3) - else if(feral < 0) - turns_since_move = 0 - else if(!my_hydrotray || my_hydrotray.loc != src.loc || my_hydrotray.dead || !my_hydrotray.seed) - var/obj/machinery/portable_atmospherics/hydroponics/my_hydrotray = locate() in src.loc - if(my_hydrotray) - if(!my_hydrotray.dead && my_hydrotray.seed) - turns_per_move = rand(20,50) - else - my_hydrotray = null - - pixel_x = rand(-12,12) - pixel_y = rand(-12,12) + //if we're chasing someone, get a little bit angry + if(target_mob && prob(5)) + feral++ + if(target_turf) + if (!(DirBlocked(get_step(src, get_dir(src,target_turf)),get_dir(src,target_turf)))) // Check for windows and doors! + Move(get_step(src, get_dir(src,target_turf))) + if (prob(10)) + src.visible_message("The bees swarm after [target_mob]!") + if(src.loc == target_turf) + target_turf = null + wander = 1 + else + //find some flowers, harvest + //angry bee swarms don't hang around + if(feral > 0) + turns_per_move = rand(1,3) + else if(feral < 0) + turns_since_move = 0 + else if(!my_hydrotray || my_hydrotray.loc != src.loc || my_hydrotray.dead || !my_hydrotray.seed) + var/obj/machinery/portable_atmospherics/hydroponics/my_hydrotray = locate() in src.loc + if(my_hydrotray) + if(!my_hydrotray.dead && my_hydrotray.seed) + turns_per_move = rand(20,50) + else + my_hydrotray = null + animate(src, pixel_x = rand(-12, 12), pixel_y = rand(-12, 12), time = 0.5) /mob/living/simple_animal/bee/update_icons() @@ -234,8 +226,7 @@ //Kill it with fire! /mob/living/simple_animal/bee/adjustFireLoss(damage) - damage *= 2 - ..() + ..(damage * 2) //No more grabbing bee swarms diff --git a/code/modules/mob/living/simple_animal/constructs/constructs.dm b/code/modules/mob/living/simple_animal/constructs/constructs.dm index e6b5949f1a1..f7550eca204 100644 --- a/code/modules/mob/living/simple_animal/constructs/constructs.dm +++ b/code/modules/mob/living/simple_animal/constructs/constructs.dm @@ -40,6 +40,8 @@ var/list/construct_spells = list() var/can_repair = 0 + var/health_prefix = "" + /mob/living/simple_animal/construct/cultify() return @@ -114,6 +116,7 @@ icon_living = "behemoth" maxHealth = 400 health = 400 + health_prefix = "juggernaut" response_harm = "harmlessly punches" harm_intent_damage = 0 melee_damage_lower = 30 @@ -165,6 +168,7 @@ icon_living = "floating" maxHealth = 75 health = 75 + health_prefix = "wraith" melee_damage_lower = 25 melee_damage_upper = 25 attacktext = "slashed" @@ -196,6 +200,7 @@ icon_living = "artificer" maxHealth = 50 health = 50 + health_prefix = "artificer" response_harm = "viciously beaten" harm_intent_damage = 5 melee_damage_lower = 5 @@ -225,6 +230,7 @@ icon_living = "behemoth" maxHealth = 750 health = 750 + health_prefix = "juggernaut" speak_emote = list("rumbles") response_harm = "harmlessly punched" harm_intent_damage = 0 @@ -252,6 +258,7 @@ icon_living = "harvester" maxHealth = 150 health = 150 + health_prefix = "harvester" melee_damage_lower = 25 melee_damage_upper = 25 attacktext = "violently stabbed" @@ -297,84 +304,44 @@ /mob/living/simple_animal/construct/Life() . = ..() if(.) + var/newstate if(fire) - if(fire_alert) fire.icon_state = "fire1" - else fire.icon_state = "fire0" + newstate = fire_alert ? "fire1" : "fire0" + if (fire.icon_state != newstate) + fire.icon_state = newstate + if(pullin) - if(pulling) pullin.icon_state = "pull1" - else pullin.icon_state = "pull0" + newstate = pulling ? "pull1" : "pull0" + if (pullin.icon_state != newstate) + pullin.icon_state = newstate if(purged) - if(purge > 0) purged.icon_state = "purge1" - else purged.icon_state = "purge0" + newstate = purge > 0 ? "purge1" : "purge0" + if (purged.icon_state != newstate) + purged.icon_state = newstate silence_spells(purge) -/mob/living/simple_animal/construct/armoured/Life() - ..() - if(healths) - switch(health) - if(250 to INFINITY) healths.icon_state = "juggernaut_health0" - if(208 to 249) healths.icon_state = "juggernaut_health1" - if(167 to 207) healths.icon_state = "juggernaut_health2" - if(125 to 166) healths.icon_state = "juggernaut_health3" - if(84 to 124) healths.icon_state = "juggernaut_health4" - if(42 to 83) healths.icon_state = "juggernaut_health5" - if(1 to 41) healths.icon_state = "juggernaut_health6" - else healths.icon_state = "juggernaut_health7" - - -/mob/living/simple_animal/construct/behemoth/Life() - ..() - if(healths) - switch(health) - if(750 to INFINITY) healths.icon_state = "juggernaut_health0" - if(625 to 749) healths.icon_state = "juggernaut_health1" - if(500 to 624) healths.icon_state = "juggernaut_health2" - if(375 to 499) healths.icon_state = "juggernaut_health3" - if(250 to 374) healths.icon_state = "juggernaut_health4" - if(125 to 249) healths.icon_state = "juggernaut_health5" - if(1 to 124) healths.icon_state = "juggernaut_health6" - else healths.icon_state = "juggernaut_health7" - -/mob/living/simple_animal/construct/builder/Life() - ..() - if(healths) - switch(health) - if(50 to INFINITY) healths.icon_state = "artificer_health0" - if(42 to 49) healths.icon_state = "artificer_health1" - if(34 to 41) healths.icon_state = "artificer_health2" - if(26 to 33) healths.icon_state = "artificer_health3" - if(18 to 25) healths.icon_state = "artificer_health4" - if(10 to 17) healths.icon_state = "artificer_health5" - if(1 to 9) healths.icon_state = "artificer_health6" - else healths.icon_state = "artificer_health7" - - - -/mob/living/simple_animal/construct/wraith/Life() - ..() - if(healths) - switch(health) - if(75 to INFINITY) healths.icon_state = "wraith_health0" - if(62 to 74) healths.icon_state = "wraith_health1" - if(50 to 61) healths.icon_state = "wraith_health2" - if(37 to 49) healths.icon_state = "wraith_health3" - if(25 to 36) healths.icon_state = "wraith_health4" - if(12 to 24) healths.icon_state = "wraith_health5" - if(1 to 11) healths.icon_state = "wraith_health6" - else healths.icon_state = "wraith_health7" - - -/mob/living/simple_animal/construct/harvester/Life() - ..() - if(healths) - switch(health) - if(150 to INFINITY) healths.icon_state = "harvester_health0" - if(125 to 149) healths.icon_state = "harvester_health1" - if(100 to 124) healths.icon_state = "harvester_health2" - if(75 to 99) healths.icon_state = "harvester_health3" - if(50 to 74) healths.icon_state = "harvester_health4" - if(25 to 49) healths.icon_state = "harvester_health5" - if(1 to 24) healths.icon_state = "harvester_health6" - else healths.icon_state = "harvester_health7" + if (healths) + var/health_percent = (health/maxHealth)*100 + var/newstate = 0 + switch (health_percent) + if (84 to INFINITY) + newstate = 0 + if (70 to 84) + newstate = 1 + if (56 to 70) + newstate = 2 + if (42 to 56) + newstate = 3 + if (28 to 42) + newstate = 4 + if (14 to 28) + newstate = 5 + if (1 to 14) + newstate = 6 + else + newstate = 7 + newstate = "[health_prefix][newstate]" + if (healths.icon_state != newstate) + healths.icon_state = newstate diff --git a/code/modules/mob/living/simple_animal/friendly/cat.dm b/code/modules/mob/living/simple_animal/friendly/cat.dm index 938d7470b35..7f9010aa7d5 100644 --- a/code/modules/mob/living/simple_animal/friendly/cat.dm +++ b/code/modules/mob/living/simple_animal/friendly/cat.dm @@ -34,22 +34,22 @@ pass_flags = PASSTABLE possession_candidate = 1 -/mob/living/simple_animal/cat/Life() +/mob/living/simple_animal/cat/think() //MICE! - - ..() - if (!stat) for(var/mob/living/simple_animal/mouse/snack in oview(src,7)) if(snack.stat != DEAD && prob(65))//The probability allows her to not get stuck target the first mouse, reducing exploits mousetarget = snack movement_target = snack - foodtarget = 0//chasing mice takes precedence over eating food + foodtarget = 0 //chasing mice takes precedence over eating food if(prob(15)) audible_emote(pick("hisses and spits!","mrowls fiercely!","eyes [snack] hungrily.")) + + addtimer(CALLBACK(src, .proc/attack_mice), 2) break + if(!buckled) if (turns_since_move > 5 || (flee_target || mousetarget)) walk_to(src,0) @@ -63,8 +63,6 @@ if (!movement_target) walk_to(src,0) - addtimer(CALLBACK(src, .proc/attack_mice), 2) - if(prob(2)) //spooky var/mob/dead/observer/spook = locate() in range(src,5) if(spook) @@ -89,7 +87,7 @@ if(movement_target) stop_automated_movement = 1 - walk_to(src,movement_target,0,seek_move_delay) + walk_to(src, movement_target, 0, DS2TICKS(seek_move_delay)) /mob/living/simple_animal/cat/proc/attack_mice() if((src.loc) && isturf(src.loc)) @@ -183,7 +181,7 @@ //walk to friend stop_automated_movement = 1 movement_target = friend - walk_to(src, movement_target, near_dist, seek_move_delay) + walk_to(src, movement_target, near_dist, DS2TICKS(seek_move_delay)) //already following and close enough, stop else if (current_dist <= near_dist) @@ -196,7 +194,7 @@ if (!friend || movement_target != friend) ..() -/mob/living/simple_animal/cat/fluff/Life() +/mob/living/simple_animal/cat/fluff/think() ..() if (stat || QDELETED(friend)) return diff --git a/code/modules/mob/living/simple_animal/friendly/corgi.dm b/code/modules/mob/living/simple_animal/friendly/corgi.dm index b110b499434..efc6c4141f7 100644 --- a/code/modules/mob/living/simple_animal/friendly/corgi.dm +++ b/code/modules/mob/living/simple_animal/friendly/corgi.dm @@ -45,7 +45,7 @@ response_disarm = "bops" response_harm = "kicks" -/mob/living/simple_animal/corgi/Ian/Life() +/mob/living/simple_animal/corgi/Ian/think() ..() if(!stat && !resting && !buckled) @@ -68,14 +68,16 @@ /mob/living/simple_animal/corgi/attackby(var/obj/item/O as obj, var/mob/user as mob) //Marker -Agouri if(istype(O, /obj/item/weapon/newspaper)) if(!stat) - for(var/mob/M in viewers(user, null)) - if ((M.client && !( M.blinded ))) - M.show_message("[user] baps [name] on the nose with the rolled up [O]") - scan_interval = max_scan_interval//discipline your dog to make it stop stealing food for a while - movement_target = null - foodtarget = 0 - stop_automated_movement = 0 - turns_since_scan = 0 + visible_message( + "[user] baps [src] on the nose with the rolled up [O.name].", + "[user] baps you on the nose with the rolled up [O.name]!" + ) + scan_interval = max_scan_interval + movement_target = null + foodtarget = 0 + stop_automated_movement = 0 + turns_since_scan = 0 + INVOKE_ASYNC(src, .proc/do_dance, list(1,2,4,8,4,2,1,2)) else ..() @@ -138,9 +140,8 @@ return ..() -/mob/living/simple_animal/corgi/Lisa/Life() +/mob/living/simple_animal/corgi/Lisa/think() ..() - if(!stat && !resting && !buckled) turns_since_scan++ if(turns_since_scan > 15) @@ -161,8 +162,8 @@ if(near_camera(src) || near_camera(ian)) return new /mob/living/simple_animal/corgi/puppy(loc) + puppies++ - - if(prob(1)) - visible_emote(pick("dances around","chases her tail"),0) - INVOKE_ASYNC(src, .proc/do_dance, list(1,2,4,8,4,2,1,2,4,8,4,2,1,2,4,8,4,2)) + if (!stat && !resting && !buckled && prob(1)) + visible_emote(pick("dances around","chases her tail"),0) + INVOKE_ASYNC(src, .proc/do_dance, list(1,2,4,8,4,2,1,2,4,8,4,2,1,2,4,8,4,2)) diff --git a/code/modules/mob/living/simple_animal/friendly/crab.dm b/code/modules/mob/living/simple_animal/friendly/crab.dm index 6f8fa4fae97..40ba2617567 100644 --- a/code/modules/mob/living/simple_animal/friendly/crab.dm +++ b/code/modules/mob/living/simple_animal/friendly/crab.dm @@ -23,7 +23,7 @@ var/obj/item/inventory_mask possession_candidate = 1 -/mob/living/simple_animal/crab/Life() +/mob/living/simple_animal/crab/think() ..() //CRAB movement if(!ckey && !stat) diff --git a/code/modules/mob/living/simple_animal/friendly/farm_animals.dm b/code/modules/mob/living/simple_animal/friendly/farm_animals.dm index 1cdaccd168d..2a0a9a9be5a 100644 --- a/code/modules/mob/living/simple_animal/friendly/farm_animals.dm +++ b/code/modules/mob/living/simple_animal/friendly/farm_animals.dm @@ -36,15 +36,6 @@ /mob/living/simple_animal/hostile/retaliate/goat/Life() . = ..() if(.) - //chance to go crazy and start wacking stuff - if(!enemies.len && prob(1)) - Retaliate() - - if(enemies.len && prob(10)) - enemies = list() - LoseTarget() - src.visible_message("[src] calms down.") - if(stat == CONSCIOUS) if(udder && prob(5)) udder.add_reagent("milk", rand(5, 10)) @@ -57,12 +48,22 @@ var/obj/machinery/portable_atmospherics/hydroponics/soil/invisible/SP = locate() in loc qdel(SP) - if(!pulledby) - var/obj/effect/plant/food - food = locate(/obj/effect/plant) in oview(5,loc) - if(food) - var/step = get_step_to(src, food, 0) - Move(step) +/mob/living/simple_animal/hostile/retaliate/goat/think() + ..() + //chance to go crazy and start wacking stuff + if(!enemies.len && prob(1)) + Retaliate() + + if(enemies.len && prob(10)) + enemies = list() + LoseTarget() + src.visible_message("[src] calms down.") + + if(!pulledby) + var/obj/effect/plant/food = locate(/obj/effect/plant) in oview(5,loc) + if(food) + var/step = get_step_to(src, food, 0) + Move(step) /mob/living/simple_animal/hostile/retaliate/goat/Retaliate() ..() diff --git a/code/modules/mob/living/simple_animal/friendly/lizard.dm b/code/modules/mob/living/simple_animal/friendly/lizard.dm index 89820d7733b..f39d7f37e5d 100644 --- a/code/modules/mob/living/simple_animal/friendly/lizard.dm +++ b/code/modules/mob/living/simple_animal/friendly/lizard.dm @@ -40,7 +40,7 @@ ..() /mob/living/simple_animal/lizard/death() - .=..() + . = ..() desc = "It doesn't hiss anymore." /mob/living/simple_animal/lizard/dust() diff --git a/code/modules/mob/living/simple_animal/friendly/mouse.dm b/code/modules/mob/living/simple_animal/friendly/mouse.dm index 5920fd567bf..70ac73b1799 100644 --- a/code/modules/mob/living/simple_animal/friendly/mouse.dm +++ b/code/modules/mob/living/simple_animal/friendly/mouse.dm @@ -89,11 +89,15 @@ /mob/living/simple_animal/mouse/Move() if(..()) if (prob(50)) - pixel_x += rand(-2,2) - pixel_x = Clamp(pixel_x, -10, 10) + var/new_pixelx = pixel_x + new_pixelx += rand(-2,2) + new_pixelx = Clamp(new_pixelx, -10, 10) + animate(src, pixel_x = new_pixelx, time = 0.5) else - pixel_y += rand(-2,2) - pixel_y = Clamp(pixel_y, -4, 14) + var/new_pixely = pixel_y + new_pixely += rand(-2,2) + new_pixely = Clamp(new_pixely, -4, 14) + animate(src, pixel_y = new_pixely, time = 0.5) /mob/living/simple_animal/mouse/Initialize() . = ..() diff --git a/code/modules/mob/living/simple_animal/hostile/bear.dm b/code/modules/mob/living/simple_animal/hostile/bear.dm index 1c0125e68b5..ba78b3dae7e 100644 --- a/code/modules/mob/living/simple_animal/hostile/bear.dm +++ b/code/modules/mob/living/simple_animal/hostile/bear.dm @@ -91,7 +91,7 @@ if (stance != previous) stance_step = 0 -/mob/living/simple_animal/hostile/bear/Life() +/mob/living/simple_animal/hostile/bear/think() . =..() safety = 0 if (stat != CONSCIOUS) @@ -100,7 +100,6 @@ if (life_tick % 5 == 0) update_bearmode() - //This covers cases where the bear is damaged by things it can't detect. //Most notably, security bots, beepsky kept murdering them without resistance if (health < health_last_tick && stance != HOSTILE_STANCE_TIRED) @@ -111,7 +110,6 @@ anger = max(0, anger-1) switch(stance) - if(HOSTILE_STANCE_TIRED) stop_automated_movement = 1 stance_step++ @@ -145,9 +143,6 @@ else stance_step-- - - - if (anger && found_mob) instant_aggro()//If we're angry and someone is nearby, skip waiting and charge them @@ -166,7 +161,6 @@ tire_out() return - //If we're having no luck attacking our current target if (target_mob && !Adjacent(target_mob) && turns_since_hit > 3) LoseTarget() @@ -175,7 +169,6 @@ health_last_tick = health - //Causes the bear to find and start attacking the nearest target. //This will overwrite any existing target if a different one is closer //If there are no other suitable targets, targeting will not be changed @@ -461,7 +454,7 @@ else if (forcechange) MoveToTarget() -/mob/living/simple_animal/hostile/bear/spatial/Life() +/mob/living/simple_animal/hostile/bear/spatial/think() ..() if (stat != CONSCIOUS) return @@ -482,7 +475,6 @@ if (focus_time % tactical_delay == 0) teleport_tactical(target_mob) - //Teleport away from other bears for (var/mob/living/simple_animal/hostile/bear/bear in view(world.view, get_turf(src))) if (bear != src && bear.stat != DEAD) diff --git a/code/modules/mob/living/simple_animal/hostile/commanded/commanded.dm b/code/modules/mob/living/simple_animal/hostile/commanded/commanded.dm index 055f46ab865..5aab4a5e0a7 100644 --- a/code/modules/mob/living/simple_animal/hostile/commanded/commanded.dm +++ b/code/modules/mob/living/simple_animal/hostile/commanded/commanded.dm @@ -11,18 +11,18 @@ var/retribution = 1 //whether or not they will attack us if we attack them like some kinda dick. /mob/living/simple_animal/hostile/commanded/hear_say(var/message, var/verb = "says", var/datum/language/language = null, var/alt_name = "", var/italics = 0, var/mob/speaker = null, var/sound/speech_sound, var/sound_vol) - if((speaker in friends) || speaker == master) + if(thinking_enabled && !stat && ((speaker in friends) || speaker == master)) command_buffer.Add(speaker) command_buffer.Add(lowertext(html_decode(message))) return 0 /mob/living/simple_animal/hostile/commanded/hear_radio(var/message, var/verb="says", var/datum/language/language=null, var/part_a, var/part_b, var/part_c, var/mob/speaker = null, var/hard_to_hear = 0) - if((speaker in friends) || speaker == master) + if(thinking_enabled && !stat && ((speaker in friends) || speaker == master)) command_buffer.Add(speaker) command_buffer.Add(lowertext(html_decode(message))) return 0 -/mob/living/simple_animal/hostile/commanded/Life() +/mob/living/simple_animal/hostile/commanded/think() while(command_buffer.len > 0) var/mob/speaker = command_buffer[1] var/text = command_buffer[2] @@ -31,15 +31,16 @@ var/substring = copytext(text,length(filtered_name)+1) //get rid of the name. listen(speaker,substring) command_buffer.Remove(command_buffer[1],command_buffer[2]) - . = ..() - if(.) - switch(stance) - if(COMMANDED_FOLLOW) - follow_target() - if(COMMANDED_STOP) - commanded_stop() - + ..() + switch(stance) + if(COMMANDED_FOLLOW) + follow_target() + if(COMMANDED_STOP) + commanded_stop() +/mob/living/simple_animal/hostile/commanded/on_think_disabled() + ..() + command_buffer.Cut() /mob/living/simple_animal/hostile/commanded/FindTarget(var/new_stance = HOSTILE_STANCE_ATTACK) if(!allowed_targets.len) @@ -197,4 +198,4 @@ allowed_targets += M stance = HOSTILE_STANCE_ATTACK if(M in friends) - friends -= M \ No newline at end of file + friends -= M diff --git a/code/modules/mob/living/simple_animal/hostile/giant_spider.dm b/code/modules/mob/living/simple_animal/hostile/giant_spider.dm index 04388607531..e049a44b4d9 100644 --- a/code/modules/mob/living/simple_animal/hostile/giant_spider.dm +++ b/code/modules/mob/living/simple_animal/hostile/giant_spider.dm @@ -89,7 +89,7 @@ O.implants += eggs H << "The [src] injects something into your [O.name]!" -/mob/living/simple_animal/hostile/giant_spider/Life() +/mob/living/simple_animal/hostile/giant_spider/think() ..() if(!stat) if(stance == HOSTILE_STANCE_IDLE) @@ -106,15 +106,14 @@ stop_automated_movement = 0 walk(src, 0) -/mob/living/simple_animal/hostile/giant_spider/nurse/Life() +/mob/living/simple_animal/hostile/giant_spider/nurse/think() ..() if(!stat) if(stance == HOSTILE_STANCE_IDLE) - var/list/can_see = view(src, 10) //30% chance to stop wandering and do something if(!busy && prob(30)) //first, check for potential food nearby to cocoon - for(var/mob/living/C in can_see) + for(var/mob/living/C in view(src, world.view)) if(C.stat) cocoon_target = C busy = MOVING_TO_TARGET @@ -140,8 +139,7 @@ addtimer(CALLBACK(src, .proc/finalize_eggs), 50, TIMER_UNIQUE) else //fourthly, cocoon any nearby items so those pesky pinkskins can't use them - for(var/obj/O in can_see) - + for(var/obj/O in view(src, world.view)) if(O.anchored) continue diff --git a/code/modules/mob/living/simple_animal/hostile/hostile.dm b/code/modules/mob/living/simple_animal/hostile/hostile.dm index 01c52fe893e..3e52189ac3c 100644 --- a/code/modules/mob/living/simple_animal/hostile/hostile.dm +++ b/code/modules/mob/living/simple_animal/hostile/hostile.dm @@ -149,28 +149,20 @@ ..() walk(src, 0) -/mob/living/simple_animal/hostile/Life() +/mob/living/simple_animal/hostile/think() + ..() + switch(stance) + if(HOSTILE_STANCE_IDLE) + target_mob = FindTarget() - . = ..() - if(!.) - walk(src, 0) - return 0 - if(client) - return 0 + if(HOSTILE_STANCE_ATTACK) + if(destroy_surroundings) + DestroySurroundings() + MoveToTarget() - if(!stat) - switch(stance) - if(HOSTILE_STANCE_IDLE) - target_mob = FindTarget() - - if(HOSTILE_STANCE_ATTACK) - if(destroy_surroundings) - DestroySurroundings() - MoveToTarget() - - if(HOSTILE_STANCE_ATTACKING) - if(!AttackTarget() && destroy_surroundings)//hit a window OR a mob, not both at once - DestroySurroundings() + if(HOSTILE_STANCE_ATTACKING) + if(!AttackTarget() && destroy_surroundings) //hit a window OR a mob, not both at once + DestroySurroundings() /mob/living/simple_animal/hostile/proc/OpenFire(target_mob) @@ -186,7 +178,7 @@ else Shoot(target, src.loc, src) if(casingtype) - new casingtype + new casingtype(loc) stance = HOSTILE_STANCE_IDLE target_mob = null diff --git a/code/modules/mob/living/simple_animal/parrot.dm b/code/modules/mob/living/simple_animal/parrot.dm index 76c162985fd..36fbf7647e6 100644 --- a/code/modules/mob/living/simple_animal/parrot.dm +++ b/code/modules/mob/living/simple_animal/parrot.dm @@ -266,9 +266,8 @@ /* * AI - Not really intelligent, but I'm calling it AI anyway. */ -/mob/living/simple_animal/parrot/Life() +/mob/living/simple_animal/parrot/think() ..() - //Sprite and AI update for when a parrot gets pulled if(pulledby && stat == CONSCIOUS) icon_state = "parrot_fly" diff --git a/code/modules/mob/living/simple_animal/shade.dm b/code/modules/mob/living/simple_animal/shade.dm index a5f4c71a489..e8f9d02acb5 100644 --- a/code/modules/mob/living/simple_animal/shade.dm +++ b/code/modules/mob/living/simple_animal/shade.dm @@ -32,9 +32,10 @@ /mob/living/simple_animal/shade/cultify() return -/mob/living/simple_animal/shade/Life() - ..() - OnDeathInLife() +/mob/living/simple_animal/shade/death() + visible_message("[src] lets out a contented sigh as their form unwinds.") + new /obj/item/weapon/ectoplasm(loc) + . = ..() /mob/living/simple_animal/shade/attackby(var/obj/item/O as obj, var/mob/user as mob) //Marker -Agouri if(istype(O, /obj/item/device/soulstone)) @@ -42,16 +43,6 @@ S.transfer_soul("SHADE", src, user) return -/mob/living/simple_animal/shade/proc/OnDeathInLife() - if(stat == 2) - new /obj/item/weapon/ectoplasm (src.loc) - for(var/mob/M in viewers(src, null)) - if((M.client && !( M.blinded ))) - M.show_message("[src] lets out a contented sigh as their form unwinds.") - ghostize() - qdel(src) - return - /mob/living/simple_animal/shade/can_fall() return FALSE diff --git a/code/modules/mob/living/simple_animal/simple_animal.dm b/code/modules/mob/living/simple_animal/simple_animal.dm index 0783e6cdf3a..165bc94903e 100644 --- a/code/modules/mob/living/simple_animal/simple_animal.dm +++ b/code/modules/mob/living/simple_animal/simple_animal.dm @@ -107,7 +107,7 @@ /mob/living/simple_animal/Initialize() . = ..() - seek_move_delay = (1 / seek_speed) / (world.tick_lag / 10)//number of ticks between moves + seek_move_delay = (1 / seek_speed) * 10 //number of ds between moves turns_since_scan = rand(min_scan_interval, max_scan_interval)//Randomise this at the start so animals don't sync up health = maxHealth verbs -= /mob/verb/observe @@ -177,102 +177,37 @@ handle_supernatural() process_food() - handle_foodscanning() //Movement turns_since_move++ - if (!client) - if(!stop_automated_movement && wander && !anchored) - if(isturf(src.loc) && !resting && !buckled && canmove) //This is so it only moves if it's not inside a closet, gentics machine, etc. - - if(turns_since_move >= turns_per_move) - if(!(stop_automated_movement_when_pulled && pulledby)) //Soma animals don't move when pulled - var/moving_to = 0 // otherwise it always picks 4, fuck if I know. Did I mention fuck BYOND - moving_to = pick(cardinal) - dir = moving_to //How about we turn them the direction they are moving, yay. - Move(get_step(src,moving_to)) - turns_since_move = 0 - - //Speaking - if(speak_chance) - if(rand(0,200) < speak_chance) - if(speak && speak.len) - if((emote_hear && emote_hear.len) || (emote_see && emote_see.len)) - var/length = speak.len - if(emote_hear && emote_hear.len) - length += emote_hear.len - if(emote_see && emote_see.len) - length += emote_see.len - var/randomValue = rand(1,length) - if(randomValue <= speak.len) - say(pick(speak)) - else - randomValue -= speak.len - if(emote_see && randomValue <= emote_see.len) - visible_emote("[pick(emote_see)].",0) - else - audible_emote("[pick(emote_hear)].",0) - else - say(pick(speak)) - else - if(!(emote_hear && emote_hear.len) && (emote_see && emote_see.len)) - visible_emote("[pick(emote_see)].",0) - if((emote_hear && emote_hear.len) && !(emote_see && emote_see.len)) - audible_emote("[pick(emote_hear)].",0) - if((emote_hear && emote_hear.len) && (emote_see && emote_see.len)) - var/length = emote_hear.len + emote_see.len - var/pick = rand(1,length) - if(pick <= emote_see.len) - visible_emote("[pick(emote_see)].",0) - else - audible_emote("[pick(emote_hear)].",0) - speak_audio() - - if (can_nap) - if (!resting && prob(1)) - fall_asleep() - else if (resting && (prob(0.5) || !stat)) - wake_up() - //Atmos var/atmos_suitable = 1 - var/atom/A = src.loc - - if(istype(A,/turf)) - var/turf/T = A + if(isturf(loc)) + var/turf/T = loc var/datum/gas_mixture/Environment = T.return_air() if(Environment) - - if( abs(Environment.temperature - bodytemperature) > 40 ) + if (abs(Environment.temperature - bodytemperature) > 40) bodytemperature += ((Environment.temperature - bodytemperature) / 5) - if(min_oxy) - if(Environment.gas["oxygen"] < min_oxy) - atmos_suitable = 0 - if(max_oxy) - if(Environment.gas["oxygen"] > max_oxy) - atmos_suitable = 0 - if(min_tox) - if(Environment.gas["phoron"] < min_tox) - atmos_suitable = 0 - if(max_tox) - if(Environment.gas["phoron"] > max_tox) - atmos_suitable = 0 - if(min_n2) - if(Environment.gas["nitrogen"] < min_n2) - atmos_suitable = 0 - if(max_n2) - if(Environment.gas["nitrogen"] > max_n2) - atmos_suitable = 0 - if(min_co2) - if(Environment.gas["carbon_dioxide"] < min_co2) - atmos_suitable = 0 - if(max_co2) - if(Environment.gas["carbon_dioxide"] > max_co2) - atmos_suitable = 0 + if(min_oxy && Environment.gas["oxygen"] < min_oxy) + atmos_suitable = 0 + else if(max_oxy && Environment.gas["oxygen"] > max_oxy) + atmos_suitable = 0 + else if(min_tox && Environment.gas["phoron"] < min_tox) + atmos_suitable = 0 + else if(max_tox && Environment.gas["phoron"] > max_tox) + atmos_suitable = 0 + else if(min_n2 && Environment.gas["nitrogen"] < min_n2) + atmos_suitable = 0 + else if(max_n2 && Environment.gas["nitrogen"] > max_n2) + atmos_suitable = 0 + else if(min_co2 && Environment.gas["carbon_dioxide"] < min_co2) + atmos_suitable = 0 + else if(max_co2 && Environment.gas["carbon_dioxide"] > max_co2) + atmos_suitable = 0 //Atmos effect if(bodytemperature < minbodytemp) @@ -288,12 +223,64 @@ apply_damage(unsuitable_atoms_damage, OXY, used_weapon = "Atmosphere") return 1 +/mob/living/simple_animal/think() + ..() + handle_foodscanning() + if(!stop_automated_movement && wander && !anchored) + if(isturf(loc) && !resting && !buckled && canmove) //This is so it only moves if it's not inside a closet, gentics machine, etc. + if(turns_since_move >= turns_per_move && !(stop_automated_movement_when_pulled && pulledby)) //Some animals don't move when pulled + var/moving_to = 0 // otherwise it always picks 4, fuck if I know. Did I mention fuck BYOND + moving_to = pick(cardinal) + set_dir(moving_to) //How about we turn them the direction they are moving, yay. + Move(get_step(src,moving_to)) + turns_since_move = 0 + + //Speaking + if(speak_chance && rand(0,200) < speak_chance) + if(LAZYLEN(speak)) + if(LAZYLEN(emote_hear) || LAZYLEN(emote_see)) + var/length = speak.len + if(emote_hear && emote_hear.len) + length += emote_hear.len + if(emote_see && emote_see.len) + length += emote_see.len + var/randomValue = rand(1,length) + if(randomValue <= speak.len) + say(pick(speak)) + else + randomValue -= speak.len + if(emote_see && randomValue <= emote_see.len) + visible_emote("[pick(emote_see)].",0) + else + audible_emote("[pick(emote_hear)].",0) + else + say(pick(speak)) + else + if(!(emote_hear && emote_hear.len) && (emote_see && emote_see.len)) + visible_emote("[pick(emote_see)].",0) + if((emote_hear && emote_hear.len) && !(emote_see && emote_see.len)) + audible_emote("[pick(emote_hear)].",0) + if((emote_hear && emote_hear.len) && (emote_see && emote_see.len)) + var/length = emote_hear.len + emote_see.len + var/pick = rand(1,length) + if(pick <= emote_see.len) + visible_emote("[pick(emote_see)].",0) + else + audible_emote("[pick(emote_hear)].",0) + speak_audio() + + if (can_nap) + if (resting) + if (prob(1)) + fall_asleep() + else + if (!stat || prob(0.5)) + wake_up() + /mob/living/simple_animal/proc/handle_supernatural() if(purge) purge -= 1 - - //Simple reagent processing for simple animals //This allows animals to digest food, and only food //Most drugs, poisons etc, are designed to work on carbons and affect many values a simple animal doesnt have @@ -337,7 +324,7 @@ else return 2//hungry /mob/living/simple_animal/gib() - ..(icon_gib,1) + ..(icon_gib, 1) /mob/living/simple_animal/emote(var/act, var/type, var/desc) if(act) @@ -589,29 +576,18 @@ mob/living/simple_animal/bullet_act(var/obj/item/projectile/Proj) scan_interval = min_scan_interval stop_automated_movement = 1 - if (istype(movement_target.loc, /turf)) - walk_to(src,movement_target,0, seek_move_delay)//Stand ontop of food + if (isturf(movement_target.loc)) + walk_to(src, movement_target, 0, DS2TICKS(seek_move_delay)) //Stand ontop of food else - walk_to(src,movement_target.loc,1, seek_move_delay)//Don't stand ontop of people + walk_to(src, get_turf(movement_target), 1, DS2TICKS(seek_move_delay)) //Don't stand ontop of people - - - if(movement_target) //Not redundant due to sleeps, Item can be gone in 6 decisecomds - if (movement_target.loc.x < src.x) - set_dir(WEST) - else if (movement_target.loc.x > src.x) - set_dir(EAST) - else if (movement_target.loc.y < src.y) - set_dir(SOUTH) - else if (movement_target.loc.y > src.y) - set_dir(NORTH) - else - set_dir(SOUTH) + if (movement_target) + set_dir(get_dir(src, movement_target)) if(isturf(movement_target.loc) && Adjacent(get_turf(movement_target), src)) UnarmedAttack(movement_target) - if (get_turf(movement_target) == src.loc) - set_dir(pick(1,2,4,8,1,1))//Face a random direction when eating, but mostly upwards + if (get_turf(movement_target) == loc) + set_dir(pick(NORTH, SOUTH, EAST, WEST, NORTH, NORTH))//Face a random direction when eating, but mostly upwards else if(ishuman(movement_target.loc) && Adjacent(src, get_turf(movement_target)) && prob(10)) beg(movement_target, movement_target.loc) else diff --git a/code/modules/mob/login.dm b/code/modules/mob/login.dm index f1c3232c6a1..e1420a3b4ea 100644 --- a/code/modules/mob/login.dm +++ b/code/modules/mob/login.dm @@ -24,14 +24,14 @@ log_access("Notice: [key_name(src)] has the same [matches] as [key_name(M)] (no longer logged in).",ckey=key_name(src)) /mob/Login() - player_list |= src update_Login_details() - world.update_status() + SSfeedback.update_status() - client.images = null //remove the images such as AIs being unable to see runes - client.screen = null //remove hud items just in case - if(hud_used) qdel(hud_used) //remove the hud objects + client.images.Cut() //remove the images such as AIs being unable to see runes + client.screen.Cut() //remove hud items just in case + if(hud_used) + qdel(hud_used) //remove the hud objects hud_used = new /datum/hud(src) disconnect_time = null @@ -48,3 +48,4 @@ //set macro to normal incase it was overriden (like cyborg currently does) winset(src, null, "mainwindow.macro=macro hotkey_toggle.is-checked=false input.focus=true input.background-color=#D3B5B5") + MOB_STOP_THINKING(src) diff --git a/code/modules/mob/logout.dm b/code/modules/mob/logout.dm index de0c7c10ca7..1e1e1bd0363 100644 --- a/code/modules/mob/logout.dm +++ b/code/modules/mob/logout.dm @@ -3,11 +3,13 @@ player_list -= src disconnect_time = world.realtime log_access("Logout: [key_name(src)]",ckey=key_name(src)) + SSfeedback.update_status() + if(admin_datums[src.ckey]) if (SSticker.current_state == GAME_STATE_PLAYING) //Only report this stuff if we are currently playing. var/admins_number = 0 var/admins_number_afk = 0 - for (var/client/C) + for (var/client/C in clients) if (C.holder && (C.holder.rights & (R_MOD|R_ADMIN))) admins_number++ if (C.is_afk()) @@ -21,5 +23,8 @@ else if ((admins_number - admins_number_afk) <= 0) post_webhook_event(WEBHOOK_ADMIN, list("title"="Admin has logged out", "message"="**[key_name(src)]** logged out - only AFK admins _([admins_number_afk])_ are online.")) discord_bot.send_to_admins("[key_name(src)] logged out - only AFK admins ([admins_number_afk]) are online.") + + if (mob_thinks) + MOB_START_THINKING(src) ..() return 1 diff --git a/code/modules/mob/mob.dm b/code/modules/mob/mob.dm index 25414c73651..0d9a391dd66 100644 --- a/code/modules/mob/mob.dm +++ b/code/modules/mob/mob.dm @@ -33,6 +33,8 @@ var/atom/movable/AM = src.loc LAZYREMOVE(AM.contained_mobs, src) + MOB_STOP_THINKING(src) + return ..() @@ -61,13 +63,16 @@ spell_masters = null zone_sel = null -/mob/New() +/mob/Initialize() + . = ..() mob_list += src if(stat == DEAD) dead_mob_list += src else living_mob_list += src - ..() + + if (!ckey && mob_thinks) + MOB_START_THINKING(src) /mob/proc/show_message(msg, type, alt, alt_type)//Message, type of message (1 or 2), alternative message, alt message type (1 or 2) diff --git a/code/modules/mob/mob_defines.dm b/code/modules/mob/mob_defines.dm index 2a23b6e9c9f..1724ad29bdf 100644 --- a/code/modules/mob/mob_defines.dm +++ b/code/modules/mob/mob_defines.dm @@ -222,3 +222,5 @@ gfi_layer_rotation = GFI_ROTATION_DEFDIR var/disconnect_time = null//Time of client loss, set by Logout(), for timekeeping + + var/mob_thinks = TRUE diff --git a/code/modules/spells/spells.dm b/code/modules/spells/spells.dm index d5a8217f61a..ca3e50bde35 100644 --- a/code/modules/spells/spells.dm +++ b/code/modules/spells/spells.dm @@ -3,7 +3,7 @@ /mob/Life() ..() - if(spell_masters && spell_masters.len) + if(LAZYLEN(spell_masters)) for(var/obj/screen/movable/spell_master/spell_master in spell_masters) spell_master.update_spells(0, src) diff --git a/code/world.dm b/code/world.dm index fa9d0adbce6..910e1d9c939 100644 --- a/code/world.dm +++ b/code/world.dm @@ -321,7 +321,7 @@ var/list/world_api_rate_limit = list() D.associate(directory[ckey]) /world/proc/update_status() - var/s = "" + var/list/s = list() if (config && config.server_name) s += "[config.server_name] — " @@ -363,13 +363,14 @@ var/list/world_api_rate_limit = list() else if (n > 0) features += "~[n] player" - if (config && config.hostedby) features += "hosted by [config.hostedby]" if (features) s += ": [list2text(features, ", ")]" + s = s.Join() + /* does this help? I do not know */ if (src.status != s) src.status = s