From 8863df2e5740da862da1b76e4d4e3fdbcd292070 Mon Sep 17 00:00:00 2001 From: wraith-54321 <69217972+wraith-54321@users.noreply.github.com> Date: Sun, 3 Nov 2024 15:47:01 -0800 Subject: [PATCH] Stuff for things (#3969) * basic storyteller * hm * conflicts * pain * i think it compiles? * still not perfect but it works * spit, stomach acid, legs * commit sprites and explode * me when i explode the iron floor * conflict time * no more zombies_buttons * icons * ughhh * tank speed runner health * fix tank speed * equal * icon changes to buttons and zombies * tank bloody hands * PLEASE WORK * go go go * commit * error gone * remove debug info * disable melt wall * shitty fix * Update bloater.dm * Update zombie_meteor_storm.dm * some nerfs and fixes * Update _traits.dm * jank * t * r * m * screenshots --------- Co-authored-by: RikuTheKiller <88713943+RikuTheKiller@users.noreply.github.com> Co-authored-by: Shoddd Co-authored-by: Cannibal Hunter <135169022+CannibalHunter@users.noreply.github.com> --- code/__DEFINES/mobs.dm | 4 + .../traits/monkestation/declarations.dm | 2 + code/__DEFINES/traits/monkestation/sources.dm | 2 + .../~monkestation/dcs/signals/signals_item.dm | 3 + .../signals/signals_mob/signals_mob_main.dm | 3 + code/__HELPERS/roundend.dm | 4 +- code/_globalvars/traits/_traits.dm | 1 + code/controllers/subsystem/events.dm | 2 +- code/controllers/subsystem/statpanel.dm | 2 +- code/controllers/subsystem/ticker.dm | 4 +- code/game/gamemodes/dynamic/dynamic.dm | 2 +- code/modules/events/_event.dm | 24 ++- .../carbon/human/species_types/zombies.dm | 14 +- ...atum_species_zombie_infectious_bloater.png | Bin 0 -> 1131 bytes ...datum_species_zombie_infectious_runner.png | Bin 0 -> 1109 bytes ...atum_species_zombie_infectious_spitter.png | Bin 0 -> 1128 bytes ...__datum_species_zombie_infectious_tank.png | Bin 0 -> 1110 bytes code/modules/zombie/items.dm | 14 +- icons/mob/actions/backgrounds.dmi | Bin 11893 -> 12652 bytes .../code/modules/antagonists/zombies/items.dm | 9 + .../zombies/zombie_meteor_storm.dm | 109 ++++++++++ .../zombies/zombie_types/base_zombie.dm | 194 ++++++++++++++++++ .../zombies/zombie_types/bloater.dm | 118 +++++++++++ .../zombies/zombie_types/runner.dm | 23 +++ .../zombies/zombie_types/spitter.dm | 81 ++++++++ .../antagonists/zombies/zombie_types/tank.dm | 23 +++ .../zombies/zombification_component.dm | 40 ++++ .../blueshift/components/soulcatcher_base.dm | 8 +- .../blueshift/items/handheld_soulcatcher.dm | 8 +- .../converted_events/_base_event.dm | 11 +- .../converted_events/solo/bloodcult.dm | 1 + .../converted_events/solo/clockwork_cult.dm | 1 + .../storytellers/gamemode_subsystem.dm | 157 ++++++-------- .../storytellers/storytellers/_storyteller.dm | 33 ++- .../storytellers/storytellers/brute.dm | 18 ++ .../icons/effects/mouse_pointers/feast.dmi | Bin 0 -> 370 bytes .../icons/mob/actions/actions_zombie.dmi | Bin 0 -> 1925 bytes .../zombie/special_zombie_overlays.dmi | Bin 0 -> 2030 bytes tgstation.dme | 9 + 39 files changed, 790 insertions(+), 134 deletions(-) create mode 100644 code/modules/unit_tests/screenshots/screenshot_humanoids__datum_species_zombie_infectious_bloater.png create mode 100644 code/modules/unit_tests/screenshots/screenshot_humanoids__datum_species_zombie_infectious_runner.png create mode 100644 code/modules/unit_tests/screenshots/screenshot_humanoids__datum_species_zombie_infectious_spitter.png create mode 100644 code/modules/unit_tests/screenshots/screenshot_humanoids__datum_species_zombie_infectious_tank.png create mode 100644 monkestation/code/modules/antagonists/zombies/items.dm create mode 100644 monkestation/code/modules/antagonists/zombies/zombie_meteor_storm.dm create mode 100644 monkestation/code/modules/antagonists/zombies/zombie_types/base_zombie.dm create mode 100644 monkestation/code/modules/antagonists/zombies/zombie_types/bloater.dm create mode 100644 monkestation/code/modules/antagonists/zombies/zombie_types/runner.dm create mode 100644 monkestation/code/modules/antagonists/zombies/zombie_types/spitter.dm create mode 100644 monkestation/code/modules/antagonists/zombies/zombie_types/tank.dm create mode 100644 monkestation/code/modules/antagonists/zombies/zombification_component.dm create mode 100644 monkestation/code/modules/storytellers/storytellers/brute.dm create mode 100644 monkestation/icons/effects/mouse_pointers/feast.dmi create mode 100644 monkestation/icons/mob/actions/actions_zombie.dmi create mode 100644 monkestation/icons/mob/species/zombie/special_zombie_overlays.dmi diff --git a/code/__DEFINES/mobs.dm b/code/__DEFINES/mobs.dm index 7b823fb9db17..ae2897289448 100644 --- a/code/__DEFINES/mobs.dm +++ b/code/__DEFINES/mobs.dm @@ -125,6 +125,10 @@ #define SPECIES_VAMPIRE "vampire" #define SPECIES_ZOMBIE "zombie" #define SPECIES_ZOMBIE_INFECTIOUS "memezombie" +#define SPECIES_ZOMBIE_INFECTIOUS_RUNNER "runnerzombie" //Monkestation Addition +#define SPECIES_ZOMBIE_INFECTIOUS_TANK "tankzombie" //monkestation edit +#define SPECIES_ZOMBIE_INFECTIOUS_SPITTER "spitterzombie" //monkestation edit +#define SPECIES_ZOMBIE_INFECTIOUS_BLOATER "bloaterzombie" //monkestation edit #define SPECIES_ZOMBIE_KROKODIL "krokodil_zombie" #define SPECIES_OOZELING "oozeling" #define SPECIES_IPC "ipc" diff --git a/code/__DEFINES/traits/monkestation/declarations.dm b/code/__DEFINES/traits/monkestation/declarations.dm index 55ec36a60b8d..40e82200d5e2 100644 --- a/code/__DEFINES/traits/monkestation/declarations.dm +++ b/code/__DEFINES/traits/monkestation/declarations.dm @@ -55,6 +55,8 @@ #define TRAIT_GHOST_CRITTER "ghost_critter" /// This mob is *currently* being flashed by someone with CAN_BYPASS_INNATE_FLASH_RESISTANCE returning TRUE. Used to make IPCs not immune to rev and bb conversions. #define TRAIT_CONVERSION_FLASHED "conversion_flashed" +/// For when a mob has been consumed by a zombie +#define TRAIT_ZOMBIE_CONSUMED "zombie_consumed" // /datum/mind + /mob/living /// Prevents the user from casting spells using sign language. Works on both /datum/mind and /mob/living. diff --git a/code/__DEFINES/traits/monkestation/sources.dm b/code/__DEFINES/traits/monkestation/sources.dm index 057086421db2..8bd486c45502 100644 --- a/code/__DEFINES/traits/monkestation/sources.dm +++ b/code/__DEFINES/traits/monkestation/sources.dm @@ -32,3 +32,5 @@ #define FRENZY_TRAIT "frenzy_trait" /// Source trait for slashers. #define TRAIT_SLASHER "slasher_trait" +/// Source trait for zombies +#define ZOMBIE_TRAIT "zombie_trait" diff --git a/code/__DEFINES/~monkestation/dcs/signals/signals_item.dm b/code/__DEFINES/~monkestation/dcs/signals/signals_item.dm index b913dd94bfe1..99615d4ef0fd 100644 --- a/code/__DEFINES/~monkestation/dcs/signals/signals_item.dm +++ b/code/__DEFINES/~monkestation/dcs/signals/signals_item.dm @@ -18,3 +18,6 @@ #define COMSIG_CHECK_TURF_CLOCKWORK "check_turf_clockwork" #define COMSIG_ITEM_DAMAGE_MULTIPLIER "damage_multi_item" + +///Sent by a tumor when its removed +#define COMSIG_ZOMBIE_TUMOR_REMOVED "zombie_tumor_removed" diff --git a/code/__DEFINES/~monkestation/dcs/signals/signals_mob/signals_mob_main.dm b/code/__DEFINES/~monkestation/dcs/signals/signals_mob/signals_mob_main.dm index a31bed15e891..e0745d27fd0b 100644 --- a/code/__DEFINES/~monkestation/dcs/signals/signals_mob/signals_mob_main.dm +++ b/code/__DEFINES/~monkestation/dcs/signals/signals_mob/signals_mob_main.dm @@ -47,3 +47,6 @@ /// Initiates a nightmare snuff check (eats dim lights on everything within 2 tiles) with the given args. (turf/start_turf) #define COMSIG_NIGHTMARE_SNUFF_CHECK "nightmare_snuff_check" + +/// From base of /datum/species/zombie/infectious/proc/set_consumed_flesh(): (new_amount, old_amount) +#define COMSIG_ZOMBIE_FLESH_ADJUSTED "zombie_flesh_adjusted" diff --git a/code/__HELPERS/roundend.dm b/code/__HELPERS/roundend.dm index d2d05ccb8177..3be4855a5aa1 100644 --- a/code/__HELPERS/roundend.dm +++ b/code/__HELPERS/roundend.dm @@ -369,8 +369,8 @@ GLOBAL_LIST_INIT(round_end_images, world.file2list("data/image_urls.txt")) // MO /datum/controller/subsystem/ticker/proc/build_roundend_report() var/list/parts = list() - //might want to make this a full section - parts += "
[("Storyteller: [SSgamemode.storyteller ? SSgamemode.storyteller.name : "N/A"]")]
" //monkestation edit + //might want to make this a full section, monkestation edit + parts += "
[("Storyteller: [SSgamemode.current_storyteller ? SSgamemode.current_storyteller.name : "N/A"]")]
" //AI laws parts += law_report() diff --git a/code/_globalvars/traits/_traits.dm b/code/_globalvars/traits/_traits.dm index 4ebaef0b4b95..851e0fb69845 100644 --- a/code/_globalvars/traits/_traits.dm +++ b/code/_globalvars/traits/_traits.dm @@ -513,6 +513,7 @@ GLOBAL_LIST_INIT(traits_by_type, list( "TRAIT_XENO_IMMUNE" = TRAIT_XENO_IMMUNE, "TRAIT_XRAY_VISION" = TRAIT_XRAY_VISION, "TRAIT_COLD_BLOODED" = TRAIT_COLD_BLOODED, + "TRAIT_ZOMBIE_CONSUMED" = TRAIT_ZOMBIE_CONSUMED, /* "TRAIT_ADAMANTINE_EXTRACT_ARMOR" = TRAIT_ADAMANTINE_EXTRACT_ARMOR, */ /* "TRAIT_ALWAYS_WANTED" = TRAIT_ALWAYS_WANTED, */ /* "TRAIT_ANOSMIA" = TRAIT_ANOSMIA, */ diff --git a/code/controllers/subsystem/events.dm b/code/controllers/subsystem/events.dm index 53181e24e844..c41cd405df31 100644 --- a/code/controllers/subsystem/events.dm +++ b/code/controllers/subsystem/events.dm @@ -19,7 +19,7 @@ SUBSYSTEM_DEF(events) continue var/datum/round_event_control/event = new event_type if(!event.valid_for_map()) - qdel(event) + qdel(event) //highly iffy on this as it does cause issues for admins sometimes continue control += event //add it to the list of all events (controls) reschedule() diff --git a/code/controllers/subsystem/statpanel.dm b/code/controllers/subsystem/statpanel.dm index 22cbf7824c3d..d5aabfaefef2 100644 --- a/code/controllers/subsystem/statpanel.dm +++ b/code/controllers/subsystem/statpanel.dm @@ -26,7 +26,7 @@ SUBSYSTEM_DEF(statpanels) global_data = list( "Map: [SSmapping.config?.map_name || "Loading..."]", cached ? "Next Map: [cached.map_name]" : null, - "Storyteller: [!SSgamemode.secret_storyteller && SSgamemode.storyteller ? SSgamemode.storyteller.name : "Secret"]", //monkestation addition + "Storyteller: [!SSgamemode.secret_storyteller && SSgamemode.current_storyteller ? SSgamemode.current_storyteller.name : "Secret"]", //monkestation addition "Round ID: [GLOB.round_id ? GLOB.round_id : "NULL"]", "Server Time: [time2text(world.timeofday, "YYYY-MM-DD hh:mm:ss")]", "Round Time: [ROUND_TIME()]", diff --git a/code/controllers/subsystem/ticker.dm b/code/controllers/subsystem/ticker.dm index 9d87403d3f7e..7b8afe4ac44d 100755 --- a/code/controllers/subsystem/ticker.dm +++ b/code/controllers/subsystem/ticker.dm @@ -334,8 +334,8 @@ SUBSYSTEM_DEF(ticker) /datum/controller/subsystem/ticker/proc/PostSetup() set waitfor = FALSE - SSgamemode.storyteller.process(STORYTELLER_WAIT_TIME * 0.1) // we want this asap - SSgamemode.storyteller.round_started = TRUE + SSgamemode.current_storyteller.process(STORYTELLER_WAIT_TIME * 0.1) // we want this asap + SSgamemode.current_storyteller.round_started = TRUE mode.post_setup() GLOB.start_state = new /datum/station_state() GLOB.start_state.count() diff --git a/code/game/gamemodes/dynamic/dynamic.dm b/code/game/gamemodes/dynamic/dynamic.dm index f5fc0a516da8..c95880219a1b 100644 --- a/code/game/gamemodes/dynamic/dynamic.dm +++ b/code/game/gamemodes/dynamic/dynamic.dm @@ -334,7 +334,7 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1) if(ruleset.weight <= 0 || ruleset.cost <= 0) continue min_threat = min(ruleset.cost, min_threat) - var/greenshift = SSgamemode.storyteller.disable_distribution //|| (threat_level < min_threat && shown_threat < min_threat) //if both shown and real threat are below any ruleset, its extended time //monkestation edit: Makes it so greenshift is based on the storyteller + var/greenshift = SSgamemode.current_storyteller.disable_distribution //|| (threat_level < min_threat && shown_threat < min_threat) //if both shown and real threat are below any ruleset, its extended time //monkestation edit: Makes it so greenshift is based on the storyteller generate_station_goals(greenshift) . += generate_station_goal_report() diff --git a/code/modules/events/_event.dm b/code/modules/events/_event.dm index 274665a4a93c..57a7cd430330 100644 --- a/code/modules/events/_event.dm +++ b/code/modules/events/_event.dm @@ -98,9 +98,11 @@ /datum/round_event_control/proc/can_spawn_event(players_amt, allow_magic = FALSE, fake_check = FALSE) SHOULD_CALL_PARENT(TRUE) // monkestation start: event groups and storyteller stuff + if(SSgamemode.current_storyteller?.disable_distribution || SSgamemode.halted_storyteller) + return FALSE if(event_group && !GLOB.event_groups[event_group].can_run()) return FALSE - if(roundstart && ((SSticker.round_start_time && (world.time - SSticker.round_start_time) >= 2 MINUTES) || (SSgamemode.ran_roundstart && !fake_check))) + if(roundstart && (!SSgamemode.can_run_roundstart || (SSgamemode.ran_roundstart && !fake_check && !SSgamemode.current_storyteller?.ignores_roundstart))) return FALSE // monkestation end if(occurrences >= max_occurrences) @@ -123,9 +125,8 @@ return FALSE if(!check_enemies()) return FALSE - if(allowed_storytellers && ((islist(allowed_storytellers) && !is_type_in_list(SSgamemode.storyteller, allowed_storytellers)) || SSgamemode.storyteller.type != allowed_storytellers)) - return FALSE - if(SSgamemode.storyteller.disable_distribution || SSgamemode.halted_storyteller) + if(allowed_storytellers && SSgamemode.current_storyteller && ((islist(allowed_storytellers) && \ + !is_type_in_list(SSgamemode.current_storyteller, allowed_storytellers)) || SSgamemode.current_storyteller.type != allowed_storytellers)) return FALSE // monkestation end @@ -149,12 +150,13 @@ message_admins("Random Event triggering in [DisplayTimeText(RANDOM_EVENT_ADMIN_INTERVENTION_TIME)]: [name]. (CANCEL)") if(!roundstart) sleep(RANDOM_EVENT_ADMIN_INTERVENTION_TIME) - var/players_amt = get_active_player_count(alive_check = TRUE, afk_check = TRUE, human_check = TRUE) - if(!can_spawn_event(players_amt, fake_check = TRUE) && !forced) - message_admins("Second pre-condition check for [name] failed, skipping...") - return EVENT_INTERRUPTED - if(!can_spawn_event(players_amt, fake_check = TRUE) && forced) - message_admins("Second pre-condition check for [name] failed, but event forced, running event regardless this may have issues...") + + if(!can_spawn_event(get_active_player_count(alive_check = TRUE, afk_check = TRUE, human_check = TRUE), fake_check = TRUE) && !forced) + if(!forced) + message_admins("Second pre-condition check for [name] failed, skipping...") + return EVENT_INTERRUPTED + else if(forced) + message_admins("Second pre-condition check for [name] failed, but event forced, running event regardless this may have issues...") if(!triggering) return EVENT_CANCELLED //admin cancelled @@ -346,7 +348,7 @@ Runs the event if("schedule") message_admins("[key_name_admin(usr)] scheduled event [src.name].") log_admin_private("[key_name(usr)] scheduled [src.name].") - SSgamemode.storyteller.buy_event(src, src.track) + SSgamemode.current_storyteller.buy_event(src, src.track) if("force_next") if(length(src.admin_setup)) for(var/datum/event_admin_setup/admin_setup_datum in src.admin_setup) diff --git a/code/modules/mob/living/carbon/human/species_types/zombies.dm b/code/modules/mob/living/carbon/human/species_types/zombies.dm index 26b7f81597ec..9b0fe7b38226 100644 --- a/code/modules/mob/living/carbon/human/species_types/zombies.dm +++ b/code/modules/mob/living/carbon/human/species_types/zombies.dm @@ -120,11 +120,23 @@ /datum/species/zombie/infectious/on_species_gain(mob/living/carbon/C, datum/species/old_species) . = ..() - C.AddComponent(/datum/component/mutant_hands, mutant_hand_path = /obj/item/mutant_hand/zombie) + C.AddComponent(/datum/component/mutant_hands, mutant_hand_path = hand_path) //monkestation edit: replaces the original mutant_hand_path with hand_path +//monkestation edit start + for(var/datum/action/granted_action as anything in granted_action_types) + granted_action = new granted_action + granted_action.Grant(C) + granted_actions += granted_action +//monkestation edit end /datum/species/zombie/infectious/on_species_loss(mob/living/carbon/human/C, datum/species/new_species, pref_load) . = ..() qdel(C.GetComponent(/datum/component/mutant_hands)) +//monkestation edit start + for(var/datum/action/removed_action in granted_actions) + granted_actions -= removed_action + removed_action.Remove(C) + qdel(removed_action) +//monkestation edit end /datum/species/zombie/infectious/check_roundstart_eligible() return FALSE diff --git a/code/modules/unit_tests/screenshots/screenshot_humanoids__datum_species_zombie_infectious_bloater.png b/code/modules/unit_tests/screenshots/screenshot_humanoids__datum_species_zombie_infectious_bloater.png new file mode 100644 index 0000000000000000000000000000000000000000..315426c418c2c555ae4b7b95297f94919edc143d GIT binary patch literal 1131 zcmV-x1eE)UP)G5|&Z09yb6hX4R#0001B2}A$@d;kEHzqbGX|C0a!iHV7@u&^5& z8%O{E8X6it5)zJ(lQlR&QcO%%T4Nd-A#`_ue2`WY6BDzuv!u6yQBPa70021v0B!&P z5fl^{92}>rw$#_(iHo8nBq}W}Gt14fFDZ*Bkpc$`yKaB_9`^iy#0_2eo`Eh^5;&r`5f zFwryM;w;ZhDainGjE%TBGg33tGfE(w;*!LYR3KAHiHkEOv#1!Po{KZBC^0t`#5Uwo zR&e!m0ha{ z|3A0)NGDCh=T6<1shU-xM9=xIhc(=46G_UCb)+!Sf?g|A5R@>UiJ z%kQ#uQ7wv;>oUp8vdr`BjtjWY@}fxdj61~`P?p&}!T6$XhL)bgu4XquU@iva20x zB*QmN?fM?P*#s)UEm+^X+J?AjH(JTw^-gt--UI?R+H?xM$E^zT${R-wcGCE2QZaJb z(M7rt7nRrxZs%3q z^{&%?hi?IHZ0Gu}Rwls3Zd(mrst#SYeyFyS0C0<+!F5X70H+p*2le>v>HDL47=o8T z+*%vvs2GHAwccpmdiug}&2d_IRyOn(dd~iY)3egzEPw5SY-|wHF_ks@l5Anmx%VB>M$31-r^5cHw`(u!c)8TMBoyGY?A3nf8&6;Dy zcruIgd6C{!k&2|u(;~uWXv<2}-WSS_P1jsd)Y7-M~le^%a5L;~D;Yl8FNTrLepKV~c^7(!vJ zS|yvyC6Ivq22jB;_%{v&gZ#n-5^U30KPpDuX)ZuRFvu@FRsSAft-D^Ub-Dlm002ovPDHLkV1jj9|C;~+ literal 0 HcmV?d00001 diff --git a/code/modules/unit_tests/screenshots/screenshot_humanoids__datum_species_zombie_infectious_runner.png b/code/modules/unit_tests/screenshots/screenshot_humanoids__datum_species_zombie_infectious_runner.png new file mode 100644 index 0000000000000000000000000000000000000000..0e35d0ae6a57dcfdccb671252a55fec62b6ead58 GIT binary patch literal 1109 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 literal 11893 zcmd6N^;=ZIzyDd5l14x}q#FT6ky?~gIu%4(5Gm7)0|O~KmL(&e?Y+iNFB0PxDlOdND1!-yyW}nO ziL7aR>-d+UKBZCWYaXGjaf&UqyT4OdjGl$B>*;m;MUnBlE{{BNqH-SHqgh=V&x zrRmaw9(jwSCSTOmk8!!qf^K3`<5S$A6ulLp;az6QU8i#+s(F%O>gVanD^|ff2#&q7 zBMuwOzNPY{0Lti2gz!ka-C_5MZroU|23J@cPj@c;(+vFkT9WVWb=-OL)VM!VN!%)( zzoYfuYaq|mYap55hjnUp+~&v2*;xy+hLu~jVNK__G_S4ODP+�Kf*QD#+`3W$b49 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 k*3*be{8kH9Q7mh>R?T1fs 0) + to_chat(user, span_alien("In your final moments, you managed to infect [infects] [infects == 1 ? "person" : "people"].")) + + user.gib(no_brain = TRUE, no_organs = TRUE, no_bodyparts = TRUE, safe_gib = FALSE) + +/datum/action/cooldown/zombie/melt_wall + name = "Stomach Acid" + desc = "Drench an object in stomach acid, destroying it over time." + button_icon_state = "zombie_vomit" + background_icon_state = "bg_zombie" + cooldown_time = 30 SECONDS + click_to_activate = TRUE + +/datum/action/cooldown/zombie/melt_wall/set_click_ability(mob/on_who) + . = ..() + if(!.) + return + + to_chat(on_who, span_notice("You prepare to vomit. Click a target to puke on it!")) + on_who.update_icons() + +/datum/action/cooldown/zombie/melt_wall/unset_click_ability(mob/on_who, refund_cooldown = TRUE) + . = ..() + if(!.) + return + + if(refund_cooldown) + to_chat(on_who, span_notice("You empty your mouth.")) + on_who.update_icons() + +/datum/action/cooldown/zombie/melt_wall/PreActivate(atom/target) + if(get_dist(owner, target) > 1) + return FALSE + if(ismob(target)) //If it could corrode mobs, it would one-shot them. + owner.balloon_alert(owner, "doesn't work on mobs!") + return FALSE + + return ..() + +/datum/action/cooldown/zombie/melt_wall/Activate(atom/target) + if(!target.acid_act(200, 1000)) + to_chat(owner, span_notice("You cannot dissolve this object.")) + return FALSE + + owner.visible_message( + span_alert("[owner] vomits globs of vile stuff all over [target]. It begins to sizzle and melt under the bubbling mess of acid!"), + span_notice("You vomit globs of acid over [target]. It begins to sizzle and melt."), + ) + return TRUE + +/datum/action/cooldown/zombie/explode + name = "Explode" + button_icon_state = "explode" + desc = "Trigger the explosive cocktail residing in your body, causing a devastating explosion that infects nearby targets. Triggers automatically on death." + check_flags = NONE + +/datum/action/cooldown/zombie/explode/Activate(atom/target) + . = ..() + var/mob/living/user = owner + user.death() // lol diff --git a/monkestation/code/modules/antagonists/zombies/zombie_types/runner.dm b/monkestation/code/modules/antagonists/zombies/zombie_types/runner.dm new file mode 100644 index 000000000000..5425eb24f6a2 --- /dev/null +++ b/monkestation/code/modules/antagonists/zombies/zombie_types/runner.dm @@ -0,0 +1,23 @@ +//NEEDS TO BE MADE FAST +/datum/species/zombie/infectious/runner + name = "Runner Zombie" + id = SPECIES_ZOMBIE_INFECTIOUS_RUNNER + maxhealthmod = 0.7 + armor = 0 + hand_path = /obj/item/mutant_hand/zombie/low_infection/weak + granted_action_types = list() + bodypart_overlay_icon_states = list(BODY_ZONE_CHEST = "runner-chest", BODY_ZONE_HEAD = "runner-head") + bodypart_overrides = list( + BODY_ZONE_HEAD = /obj/item/bodypart/head/zombie, + BODY_ZONE_CHEST = /obj/item/bodypart/chest/zombie, + BODY_ZONE_L_ARM = /obj/item/bodypart/arm/left/zombie, + BODY_ZONE_R_ARM = /obj/item/bodypart/arm/right/zombie, + BODY_ZONE_L_LEG = /obj/item/bodypart/leg/left/zombie/runner, + BODY_ZONE_R_LEG = /obj/item/bodypart/leg/right/zombie/runner, + ) + +/obj/item/bodypart/leg/left/zombie/runner + speed_modifier = -0.2 + +/obj/item/bodypart/leg/right/zombie/runner + speed_modifier = -0.2 diff --git a/monkestation/code/modules/antagonists/zombies/zombie_types/spitter.dm b/monkestation/code/modules/antagonists/zombies/zombie_types/spitter.dm new file mode 100644 index 000000000000..b1f4adca7bc2 --- /dev/null +++ b/monkestation/code/modules/antagonists/zombies/zombie_types/spitter.dm @@ -0,0 +1,81 @@ +//UNIMPLEMENTED +//spits weak knockdown projectiles +/datum/species/zombie/infectious/spitter + name = "Spitter Zombie" + id = SPECIES_ZOMBIE_INFECTIOUS_SPITTER + bodypart_overlay_icon_states = list(BODY_ZONE_CHEST = "spitter-chest", BODY_ZONE_HEAD = "spitter_head", BODY_ZONE_R_ARM = "spitter-right-hand", BODY_ZONE_L_ARM = "spitter-left-hand") + granted_action_types = list( + /datum/action/cooldown/zombie/spit, + ) + + +//Just stole aliens stuff, needs some rewording +/datum/action/cooldown/zombie/spit + name = "Spit" + desc = "Spit at someone, causing them to fall down and get burnt." + background_icon_state = "bg_zombie" + button_icon_state = "spit_off" + cooldown_time = 8 SECONDS + click_to_activate = TRUE + +/datum/action/cooldown/zombie/spit/IsAvailable(feedback = FALSE) + if(owner.is_muzzled()) + return FALSE + + if(!isturf(owner.loc)) + return FALSE + return ..() + +/datum/action/cooldown/zombie/spit/set_click_ability(mob/on_who) + . = ..() + if(!.) + return + + to_chat(on_who, span_notice("You prepare to spit. Left-click to fire at a target!")) + + button_icon_state = "spit_on" + build_all_button_icons() + on_who.update_icons() + +/datum/action/cooldown/zombie/spit/unset_click_ability(mob/on_who, refund_cooldown = TRUE) + . = ..() + if(!.) + return + + if(refund_cooldown) + to_chat(on_who, span_notice("You empty your mouth.")) + + button_icon_state = "spit_off" + build_all_button_icons() + on_who.update_icons() + +// We do this in InterceptClickOn() instead of Activate() +// because we use the click parameters for aiming the projectile +// (or something like that) +/datum/action/cooldown/zombie/spit/InterceptClickOn(mob/living/user, params, atom/target) + . = ..() + if(!.) + unset_click_ability(user, refund_cooldown = FALSE) + return FALSE + + var/modifiers = params2list(params) + user.visible_message( + span_danger("[user] spits!"), + span_alert("You spit."), + ) + var/obj/projectile/neurotoxin/zombie/spit = new(user.loc) + spit.preparePixelProjectile(target, user, modifiers) + spit.firer = user + spit.fire() + user.newtonian_move(get_dir(target, user)) + return TRUE + +// Has to return TRUE, otherwise is skipped. +/datum/action/cooldown/zombie/spit/Activate(atom/target) + return TRUE + +/obj/projectile/neurotoxin/zombie + name = "spit" + icon_state = "glob_projectile" + damage = 20 + damage_type = BURN diff --git a/monkestation/code/modules/antagonists/zombies/zombie_types/tank.dm b/monkestation/code/modules/antagonists/zombies/zombie_types/tank.dm new file mode 100644 index 000000000000..d271059f67d4 --- /dev/null +++ b/monkestation/code/modules/antagonists/zombies/zombie_types/tank.dm @@ -0,0 +1,23 @@ +/datum/species/zombie/infectious/tank + name = "Tank Zombie" + id = SPECIES_ZOMBIE_INFECTIOUS_TANK + armor = 40 + maxhealthmod = 1.5 + heal_rate = 1 // Slightly higher regeneration rate. + hand_path = /obj/item/mutant_hand/zombie/low_infection + granted_action_types = list() + bodypart_overlay_icon_states = list(BODY_ZONE_CHEST = "tank-chest", BODY_ZONE_HEAD = "tank_head", BODY_ZONE_R_ARM = "generic-right-hand", BODY_ZONE_L_ARM = "generic-left-hand") + bodypart_overrides = list( + BODY_ZONE_HEAD = /obj/item/bodypart/head/zombie, + BODY_ZONE_CHEST = /obj/item/bodypart/chest/zombie, + BODY_ZONE_L_ARM = /obj/item/bodypart/arm/left/zombie, + BODY_ZONE_R_ARM = /obj/item/bodypart/arm/right/zombie, + BODY_ZONE_L_LEG = /obj/item/bodypart/leg/left/zombie/tank, + BODY_ZONE_R_LEG = /obj/item/bodypart/leg/right/zombie/tank, + ) + +/obj/item/bodypart/leg/left/zombie/tank + speed_modifier = 1.2 + +/obj/item/bodypart/leg/right/zombie/tank + speed_modifier = 1.2 diff --git a/monkestation/code/modules/antagonists/zombies/zombification_component.dm b/monkestation/code/modules/antagonists/zombies/zombification_component.dm new file mode 100644 index 000000000000..a6ad9b247c23 --- /dev/null +++ b/monkestation/code/modules/antagonists/zombies/zombification_component.dm @@ -0,0 +1,40 @@ +/datum/component/zombified + var/obj/item/organ/internal/zombie_infection/tumor + +///The component given to zombified mobs +/datum/component/zombified/Initialize(obj/item/organ/internal/zombie_infection/_tumor) + if(!ismob(parent)) + return COMPONENT_INCOMPATIBLE + + tumor = _tumor + +/datum/component/zombified/RegisterWithParent() + +/datum/component/zombified/UnregisterFromParent() + + +/datum/component/zombified/proc/signalproc(datum/source) + SIGNAL_HANDLER + send_to_playing_players("[source] signaled [src]!") + +/* +/datum/component/zombified/InheritComponent(datum/component/zombified/old, i_am_original, list/arguments) + myvar = old.myvar + + if(i_am_original) + send_to_playing_players("No parent should have to bury their child") +*/ + +/* +/datum/component/zombified/PreTransfer() + send_to_playing_players("Goodbye [parent], I'm getting adopted") + +/datum/component/zombified/PostTransfer() + send_to_playing_players("Hello my new parent, [parent]! It's nice to meet you!") +*/ + +/* +/datum/component/zombified/CheckDupeComponent(datum/zombified/new, myargone, myargtwo) + if(myargone == myvar) + return TRUE +*/ diff --git a/monkestation/code/modules/blueshift/components/soulcatcher_base.dm b/monkestation/code/modules/blueshift/components/soulcatcher_base.dm index 4a10e7832efa..2085a93a840d 100644 --- a/monkestation/code/modules/blueshift/components/soulcatcher_base.dm +++ b/monkestation/code/modules/blueshift/components/soulcatcher_base.dm @@ -77,7 +77,8 @@ GLOBAL_LIST_EMPTY(soulcatchers) * * target_name - The name that we want to assign to the created room. * * target_desc - The description that we want to assign to the created room. */ -/datum/component/soulcatcher/proc/create_room(target_name = "Default Room", target_desc = "An orange platform suspended in space orbited by reflective cubes of various sizes. There really isn't much here at the moment.") +/datum/component/soulcatcher/proc/create_room(target_name = "Default Room", \ + target_desc = "An orange platform suspended in space orbited by reflective cubes of various sizes. There really isn't much here at the moment.") var/datum/soulcatcher_room/created_room = new(src) created_room.name = target_name created_room.room_description = target_desc @@ -155,13 +156,14 @@ GLOBAL_LIST_EMPTY(soulcatchers) /// Checks the total number of souls present and compares it with `max_souls` returns `TRUE` if there is room (or no limit), otherwise returns `FALSE` /datum/component/soulcatcher/proc/check_for_vacancy() - if(!max_souls) + return FALSE + /*if(!max_souls) return TRUE if(length(get_current_souls()) >= max_souls) return FALSE - return TRUE + return TRUE*/ /// Attempts to remove the soulcatcher from the attached object /datum/component/soulcatcher/proc/remove_self() diff --git a/monkestation/code/modules/blueshift/items/handheld_soulcatcher.dm b/monkestation/code/modules/blueshift/items/handheld_soulcatcher.dm index 518ab620408f..198f954f6867 100644 --- a/monkestation/code/modules/blueshift/items/handheld_soulcatcher.dm +++ b/monkestation/code/modules/blueshift/items/handheld_soulcatcher.dm @@ -2,7 +2,11 @@ /obj/item/handheld_soulcatcher name = "\improper Evoker-type RSD" - desc = "The Evoker-Type Resonance Simulation Device is a sort of 'Soulcatcher' instrument that's been designated for handheld usage. These RSDs were designed with the Medical field in mind, a tool meant to offer comfort to the temporarily-departed while their bodies are being repaired, healed, or produced. The Evoker is essentially a very specialized handheld NIF, still using the same nanomachinery for the software and hardware. This careful instrument is able to host a virtual space for a great number of Engrams for an essentially indefinite amount of time in an unlimited variety of simulations, even able to transfer them to and from a NIF. However, it's best Medical practice to not lollygag." + desc = "The Evoker-Type Resonance Simulation Device is a sort of 'Soulcatcher' instrument that's been designated for handheld usage. \ + These RSDs were designed with the Medical field in mind, a tool meant to offer comfort to the temporarily-departed while their bodies are being repaired, \ + healed, or produced. The Evoker is essentially a very specialized handheld NIF, still using the same nanomachinery for the software and hardware. \ + This careful instrument is able to host a virtual space for a great number of Engrams for an essentially indefinite amount of time in an \ + unlimited variety of simulations, even able to transfer them to and from a NIF. However, it's best Medical practice to not lollygag." icon = 'monkestation/code/modules/blueshift/icons/obj/devices.dmi' icon_state = "soulcatcher-device" inhand_icon_state = "electronic" @@ -24,7 +28,7 @@ /obj/item/handheld_soulcatcher/attack_self(mob/user, modifiers) linked_soulcatcher.ui_interact(user) -/obj/item/handheld_soulcatcher/New(loc, ...) +/obj/item/handheld_soulcatcher/Initialize(mapload) . = ..() linked_soulcatcher = AddComponent(/datum/component/soulcatcher) linked_soulcatcher.name = name diff --git a/monkestation/code/modules/storytellers/converted_events/_base_event.dm b/monkestation/code/modules/storytellers/converted_events/_base_event.dm index 4335c4386191..d3503aa18710 100644 --- a/monkestation/code/modules/storytellers/converted_events/_base_event.dm +++ b/monkestation/code/modules/storytellers/converted_events/_base_event.dm @@ -145,6 +145,8 @@ /// A list of extra events to force whenever this one is chosen by the storyteller. /// Can either be normal list or a weighted list. var/list/extra_spawned_events + /// Similar to extra_spawned_events however these are only used by roundstart events and will only try and run if we have the points to do so + var/list/preferred_events /datum/round_event_control/antagonist/solo/from_ghosts/get_candidates() var/round_started = SSticker.HasRoundStarted() @@ -210,13 +212,18 @@ var/prompted_picking = FALSE //TODO: Implement this /// DO NOT SET THIS MANUALLY, THIS IS INHERITED FROM THE EVENT CONTROLLER ON NEW var/list/extra_spawned_events + // Same as above + var/list/preferred_events /datum/round_event/antagonist/solo/New(my_processing, datum/round_event_control/event_controller) . = ..() if(istype(event_controller, /datum/round_event_control/antagonist/solo)) var/datum/round_event_control/antagonist/solo/antag_event_controller = event_controller - if(antag_event_controller?.extra_spawned_events) - extra_spawned_events = fill_with_ones(antag_event_controller.extra_spawned_events) + if(antag_event_controller) + if(antag_event_controller.extra_spawned_events) + extra_spawned_events = fill_with_ones(antag_event_controller.extra_spawned_events) + if(antag_event_controller.preferred_events) + preferred_events = fill_with_ones(antag_event_controller.preferred_events) /datum/round_event/antagonist/solo/setup() var/datum/round_event_control/antagonist/solo/cast_control = control diff --git a/monkestation/code/modules/storytellers/converted_events/solo/bloodcult.dm b/monkestation/code/modules/storytellers/converted_events/solo/bloodcult.dm index 6537de94516e..378199bb53d4 100644 --- a/monkestation/code/modules/storytellers/converted_events/solo/bloodcult.dm +++ b/monkestation/code/modules/storytellers/converted_events/solo/bloodcult.dm @@ -40,6 +40,7 @@ weight = 4 max_occurrences = 1 event_icon_state = "cult" + preferred_events = list(/datum/round_event_control/antagonist/solo/clockcult = 1) /datum/round_event/antagonist/solo/bloodcult excute_round_end_reports = TRUE diff --git a/monkestation/code/modules/storytellers/converted_events/solo/clockwork_cult.dm b/monkestation/code/modules/storytellers/converted_events/solo/clockwork_cult.dm index 90c8a533750d..98f2a7adbbf8 100644 --- a/monkestation/code/modules/storytellers/converted_events/solo/clockwork_cult.dm +++ b/monkestation/code/modules/storytellers/converted_events/solo/clockwork_cult.dm @@ -39,6 +39,7 @@ weight = 0 max_occurrences = 1 event_icon_state = "clockcult" + preferred_events = list(/datum/round_event_control/antagonist/solo/bloodcult = 1) /datum/round_event/antagonist/solo/clockcult end_when = 60000 diff --git a/monkestation/code/modules/storytellers/gamemode_subsystem.dm b/monkestation/code/modules/storytellers/gamemode_subsystem.dm index b8a1179eb9b7..d7e055683043 100644 --- a/monkestation/code/modules/storytellers/gamemode_subsystem.dm +++ b/monkestation/code/modules/storytellers/gamemode_subsystem.dm @@ -3,6 +3,8 @@ #define DEFAULT_STORYTELLER_VOTE_OPTIONS 4 ///amount of players we can have before no longer running votes for storyteller #define MAX_POP_FOR_STORYTELLER_VOTE 25 +///the duration into the round for which roundstart events are still valid to run +#define ROUNDSTART_VALID_TIMEFRAME 3 MINUTES SUBSYSTEM_DEF(gamemode) name = "Gamemode" @@ -15,7 +17,7 @@ SUBSYSTEM_DEF(gamemode) /// List of our event tracks for fast access during for loops. var/list/event_tracks = EVENT_TRACKS /// Our storyteller. They progresses our trackboards and picks out events - var/datum/storyteller/storyteller + var/datum/storyteller/current_storyteller /// Result of the storyteller vote/pick. Defaults to the guide. var/selected_storyteller = /datum/storyteller/guide /// List of all the storytellers. Populated at init. Associative from type @@ -116,13 +118,10 @@ SUBSYSTEM_DEF(gamemode) var/list/control = list() //list of all datum/round_event_control. Used for selecting events based on weight and occurrences. var/list/running = list() //list of all existing /datum/round_event var/list/round_end_data = list() //list of all reports that need to add round end reports - var/list/currentrun = list() /// List of all uncategorized events, because they were wizard or holiday events var/list/uncategorized = list() - var/list/holidays //List of all holidays occuring today or null if no holidays - /// Event frequency multiplier, it exists because wizard, eugh. var/event_frequency_multiplier = 1 @@ -155,7 +154,10 @@ SUBSYSTEM_DEF(gamemode) /// What is our currently desired/selected roundstart event var/datum/round_event_control/antagonist/solo/current_roundstart_event var/list/last_round_events = list() + /// Has a roundstart event been run var/ran_roundstart = FALSE + /// Are we able to run roundstart events + var/can_run_roundstart = TRUE var/list/triggered_round_events = list() /datum/controller/subsystem/gamemode/Initialize(time, zlevel) @@ -170,12 +172,12 @@ SUBSYSTEM_DEF(gamemode) for(var/datum/round_event_control/event_type as anything in typesof(/datum/round_event_control)) if(!event_type::typepath || !event_type::name) continue + var/datum/round_event_control/event = new event_type if(!event.valid_for_map()) qdel(event) continue // event isn't good for this map no point in trying to add it to the list control += event //add it to the list of all events (controls) - getHoliday() load_config_vars() load_event_config_vars() @@ -193,10 +195,33 @@ SUBSYSTEM_DEF(gamemode) return SS_INIT_NO_NEED return SS_INIT_SUCCESS - /datum/controller/subsystem/gamemode/fire(resumed = FALSE) - if(!resumed) - src.currentrun = running.Copy() + if(SSticker.round_start_time && (world.time - SSticker.round_start_time) >= ROUNDSTART_VALID_TIMEFRAME) + can_run_roundstart = FALSE + else if(current_roundstart_event && length(current_roundstart_event.preferred_events)) //note that this implementation is made for preferred_events being other roundstart events + var/list/preferred_copy = current_roundstart_event.preferred_events.Copy() + var/datum/round_event_control/selected_event = pick_weight(preferred_copy) + var/player_count = get_active_player_count(alive_check = TRUE, afk_check = TRUE, human_check = TRUE) + if(ispath(selected_event)) //get the instances if we dont have them + current_roundstart_event.preferred_events = list() + for(var/datum/round_event_control/e_control as anything in preferred_copy) + current_roundstart_event.preferred_events[new e_control] = preferred_copy[e_control] + preferred_copy = current_roundstart_event.preferred_events.Copy() + selected_event = null + else if(!selected_event.can_spawn_event(player_count)) + preferred_copy -= selected_event + selected_event = null + + var/sanity = 0 + while(!selected_event && length(preferred_copy) && sanity < 100) + sanity++ + selected_event = pick_weight(preferred_copy) + if(!selected_event.can_spawn_event(player_count)) + preferred_copy -= selected_event + selected_event = null + + if(selected_event) + current_storyteller.try_buy_event(GLOB.event_groups) ///Handle scheduled events for(var/datum/scheduled_event/sch_event in scheduled_events) @@ -207,24 +232,11 @@ SUBSYSTEM_DEF(gamemode) sch_event.alerted_admins = TRUE message_admins("Scheduled Event: [sch_event.event] will run in [(sch_event.start_time - world.time) / 10] seconds. (CANCEL) (REFUND)") - if(!halted_storyteller && next_storyteller_process <= world.time && storyteller) + if(!halted_storyteller && next_storyteller_process <= world.time && current_storyteller) // We update crew information here to adjust population scalling and event thresholds for the storyteller. update_crew_infos() next_storyteller_process = world.time + STORYTELLER_WAIT_TIME - storyteller.process(STORYTELLER_WAIT_TIME * 0.1) - - //cache for sanic speed (lists are references anyways) - var/list/currentrun = src.currentrun - - while(currentrun.len) - var/datum/thing = currentrun[currentrun.len] - currentrun.len-- - if(thing) - thing.process(wait * 0.1) - else - running.Remove(thing) - if (MC_TICK_CHECK) - return + current_storyteller.process(STORYTELLER_WAIT_TIME * 0.1) /// Gets the number of antagonists the antagonist injection events will stop rolling after. /datum/controller/subsystem/gamemode/proc/get_antag_cap() @@ -347,7 +359,7 @@ SUBSYSTEM_DEF(gamemode) /// We roll points to be spent for roundstart events, including antagonists. /datum/controller/subsystem/gamemode/proc/roll_pre_setup_points() - if(storyteller.disable_distribution || halted_storyteller) + if(current_storyteller.disable_distribution || halted_storyteller) return /// Distribute points for(var/track in event_track_points) @@ -371,12 +383,12 @@ SUBSYSTEM_DEF(gamemode) gain_amt = ROUNDSTART_OBJECTIVES_GAIN var/calc_value = base_amt + (gain_amt * ready_players) calc_value *= roundstart_point_multipliers[track] - calc_value *= storyteller.starting_point_multipliers[track] - calc_value *= (rand(100 - storyteller.roundstart_points_variance,100 + storyteller.roundstart_points_variance)/100) + calc_value *= current_storyteller.starting_point_multipliers[track] + calc_value *= (rand(100 - current_storyteller.roundstart_points_variance,100 + current_storyteller.roundstart_points_variance)/100) event_track_points[track] = round(calc_value) /// If the storyteller guarantees an antagonist roll, add points to make it so. - if(storyteller.guarantees_roundstart_roleset && event_track_points[EVENT_TRACK_ROLESET] < point_thresholds[EVENT_TRACK_ROLESET]) + if(current_storyteller.guarantees_roundstart_roleset && event_track_points[EVENT_TRACK_ROLESET] < point_thresholds[EVENT_TRACK_ROLESET]) event_track_points[EVENT_TRACK_ROLESET] = point_thresholds[EVENT_TRACK_ROLESET] /// If we have any forced events, ensure we get enough points for them @@ -392,13 +404,13 @@ SUBSYSTEM_DEF(gamemode) /// Because roundstart events need 2 steps of firing for purposes of antags, here is the first step handled, happening before occupation division. /datum/controller/subsystem/gamemode/proc/handle_pre_setup_roundstart_events() - if(storyteller.disable_distribution) + if(current_storyteller.disable_distribution) return if(halted_storyteller) message_admins("WARNING: Didn't roll roundstart events (including antagonists) due to the storyteller being halted.") return while(TRUE) - if(!storyteller.handle_tracks()) + if(!current_storyteller.handle_tracks()) break /// Second step of handlind roundstart events, happening after people spawn. @@ -503,56 +515,6 @@ SUBSYSTEM_DEF(gamemode) /datum/admins/proc/forceGamemode(mob/user) SSgamemode.admin_panel(user) - -////////////// -// HOLIDAYS // -////////////// -//Uncommenting ALLOW_HOLIDAYS in config.txt will enable holidays - -//It's easy to add stuff. Just add a holiday datum in code/modules/holiday/holidays.dm -//You can then check if it's a special day in any code in the game by doing if(SSgamemode.holidays["Groundhog Day"]) - -//You can also make holiday random events easily thanks to Pete/Gia's system. -//simply make a random event normally, then assign it a holidayID string which matches the holiday's name. -//Anything with a holidayID, which isn't in the holidays list, will never occur. - -//Please, Don't spam stuff up with stupid stuff (key example being april-fools Pooh/ERP/etc), -//And don't forget: CHECK YOUR CODE!!!! We don't want any zero-day bugs which happen only on holidays and never get found/fixed! - -////////////////////////////////////////////////////////////////////////////////////////////////////////// -//ALSO, MOST IMPORTANTLY: Don't add stupid stuff! Discuss bonus content with Project-Heads first please!// -////////////////////////////////////////////////////////////////////////////////////////////////////////// - - -//sets up the holidays and holidays list -/datum/controller/subsystem/gamemode/proc/getHoliday() - if(!CONFIG_GET(flag/allow_holidays)) - return // Holiday stuff was not enabled in the config! - for(var/H in subtypesof(/datum/holiday)) - var/datum/holiday/holiday = new H() - var/delete_holiday = TRUE - for(var/timezone in holiday.timezones) - var/time_in_timezone = world.realtime + timezone HOURS - - var/YYYY = text2num(time2text(time_in_timezone, "YYYY")) // get the current year - var/MM = text2num(time2text(time_in_timezone, "MM")) // get the current month - var/DD = text2num(time2text(time_in_timezone, "DD")) // get the current day - var/DDD = time2text(time_in_timezone, "DDD") // get the current weekday - - if(holiday.shouldCelebrate(DD, MM, YYYY, DDD)) - holiday.celebrate() - LAZYSET(holidays, holiday.name, holiday) - delete_holiday = FALSE - break - if(delete_holiday) - qdel(holiday) - - if(holidays) - holidays = shuffle(holidays) - // regenerate station name because holiday prefixes. - set_station_name(new_station_name()) - world.update_status() - /datum/controller/subsystem/gamemode/proc/toggleWizardmode() wizardmode = !wizardmode //TODO: decide what to do with wiz events message_admins("Summon Events has been [wizardmode ? "enabled, events will occur [SSgamemode.event_frequency_multiplier] times as fast" : "disabled"]!") @@ -582,9 +544,9 @@ SUBSYSTEM_DEF(gamemode) if(SSdbcore.Connect()) var/list/to_set = list() var/arguments = list() - if(storyteller) + if(current_storyteller) to_set += "game_mode = :game_mode" - arguments["game_mode"] = storyteller.name + arguments["game_mode"] = current_storyteller.name if(GLOB.revdata.originmastercommit) to_set += "commit_hash = :commit_hash" arguments["commit_hash"] = GLOB.revdata.originmastercommit @@ -865,21 +827,21 @@ SUBSYSTEM_DEF(gamemode) /datum/controller/subsystem/gamemode/proc/set_storyteller(passed_type) if(!storytellers[passed_type]) message_admins("Attempted to set an invalid storyteller type: [passed_type], force setting to guide instead.") - storyteller = storytellers[/datum/storyteller/guide] //if we dont have any then we brick, lets not do that + current_storyteller = storytellers[/datum/storyteller/guide] //if we dont have any then we brick, lets not do that CRASH("Attempted to set an invalid storyteller type: [passed_type].") - storyteller = storytellers[passed_type] + current_storyteller = storytellers[passed_type] if(!secret_storyteller) - send_to_playing_players(span_notice("Storyteller is [storyteller.name]!")) - send_to_playing_players(span_notice("[storyteller.welcome_text]")) + send_to_playing_players(span_notice("Storyteller is [current_storyteller.name]!")) + send_to_playing_players(span_notice("[current_storyteller.welcome_text]")) else - send_to_observers(span_boldbig("Storyteller is [storyteller.name]!")) //observers still get to know + send_to_observers(span_boldbig("Storyteller is [current_storyteller.name]!")) //observers still get to know /// Panel containing information, variables and controls about the gamemode and scheduled event /datum/controller/subsystem/gamemode/proc/admin_panel(mob/user) update_crew_infos() var/round_started = SSticker.HasRoundStarted() var/list/dat = list() - dat += "Storyteller: [storyteller ? "[storyteller.name]" : "None"] " + dat += "Storyteller: [current_storyteller ? "[current_storyteller.name]" : "None"] " dat += " HALT Storyteller Event Panel Set Storyteller Refresh" dat += "
Storyteller determines points gained, event chances, and is the entity responsible for rolling events." dat += "
Active Players: [active_players] (Head: [head_crew], Sec: [sec_crew], Eng: [eng_crew], Med: [med_crew])" @@ -995,15 +957,15 @@ SUBSYSTEM_DEF(gamemode) /// Panel containing information and actions regarding events /datum/controller/subsystem/gamemode/proc/event_panel(mob/user) var/list/dat = list() - if(storyteller) - dat += "Storyteller: [storyteller.name]" - dat += "
Repetition penalty multiplier: [storyteller.event_repetition_multiplier]" - dat += "
Cost variance: [storyteller.cost_variance]" - if(storyteller.tag_multipliers) + if(current_storyteller) + dat += "Storyteller: [current_storyteller.name]" + dat += "
Repetition penalty multiplier: [current_storyteller.event_repetition_multiplier]" + dat += "
Cost variance: [current_storyteller.cost_variance]" + if(current_storyteller.tag_multipliers) dat += "
Tag multipliers:" - for(var/tag in storyteller.tag_multipliers) - dat += "[tag]:[storyteller.tag_multipliers[tag]] | " - storyteller.calculate_weights(statistics_track_page) + for(var/tag in current_storyteller.tag_multipliers) + dat += "[tag]:[current_storyteller.tag_multipliers[tag]] | " + current_storyteller.calculate_weights(statistics_track_page) else dat += "Storyteller: None
Weight and chance statistics will be inaccurate due to the present lack of a storyteller." dat += "
Roundstart Events Forced Roundstart events will use rolled points, and are guaranteed to trigger (even if the used points are not enough)" @@ -1163,8 +1125,8 @@ SUBSYSTEM_DEF(gamemode) message_admins("[key_name_admin(usr)] invoked next event for [track] track.") log_admin_private("[key_name(usr)] invoked next event for [track] track.") event_track_points[track] = point_thresholds[track] - if(storyteller) - storyteller.handle_tracks() + if(current_storyteller) + current_storyteller.handle_tracks() admin_panel(user) if("stats") switch(href_list["action"]) @@ -1211,3 +1173,4 @@ SUBSYSTEM_DEF(gamemode) #undef DEFAULT_STORYTELLER_VOTE_OPTIONS #undef MAX_POP_FOR_STORYTELLER_VOTE +#undef ROUNDSTART_VALID_TIMEFRAME diff --git a/monkestation/code/modules/storytellers/storytellers/_storyteller.dm b/monkestation/code/modules/storytellers/storytellers/_storyteller.dm index bd6486890c67..cbb8955f593f 100644 --- a/monkestation/code/modules/storytellers/storytellers/_storyteller.dm +++ b/monkestation/code/modules/storytellers/storytellers/_storyteller.dm @@ -59,7 +59,7 @@ ///weight this has of being picked for random storyteller/showing up in the vote if not always_votable var/weight = 0 -/datum/storyteller/process(delta_time) +/datum/storyteller/process(seconds_per_tick) if(!round_started || disable_distribution) // we are differing roundstarted ones until base roundstart so we can get cooler stuff return @@ -73,17 +73,15 @@ SSgamemode.forced_next_events -= EVENT_TRACK_ROLESET log_storyteller("Running SSgamemode.current_roundstart_event\[[SSgamemode.current_roundstart_event]\]") - SSgamemode.current_roundstart_event = null - if(!ignores_roundstart) - SSgamemode.ran_roundstart = TRUE + SSgamemode.ran_roundstart = TRUE - add_points(delta_time) + add_points(seconds_per_tick) handle_tracks() /// Add points to all tracks while respecting the multipliers. -/datum/storyteller/proc/add_points(delta_time) +/datum/storyteller/proc/add_points(seconds_per_tick) var/datum/controller/subsystem/gamemode/mode = SSgamemode - var/base_point = EVENT_POINT_GAINED_PER_SECOND * delta_time * mode.event_frequency_multiplier + var/base_point = EVENT_POINT_GAINED_PER_SECOND * seconds_per_tick * mode.event_frequency_multiplier for(var/track in mode.event_track_points) var/point_gain = base_point * point_gains_multipliers[track] * mode.point_gain_multipliers[track] if(mode.allow_pop_scaling) @@ -150,8 +148,26 @@ buy_event(picked_event, track, are_forced) . = TRUE +///Attempt to buy a specific event if we can afford it, otherwise returns FALSE, note this does NOT take cost variance into account +/datum/storyteller/proc/try_buy_event(datum/round_event_control/bought_event) + if(ispath(bought_event)) + bought_event = locate(bought_event) in SSevents.control //might be able to make this slightly cheaper by searching in the track sorted list + var/track = bought_event.track + if(!track || (bought_event in SSgamemode.uncategorized)) + return FALSE //trackless events cant be bought + + var/datum/controller/subsystem/gamemode/mode = SSgamemode + if(mode.event_track_points[track] - (bought_event.cost * mode.point_thresholds[track]) < 0) + return FALSE + + buy_event(bought_event, track) + return TRUE + /// Find and buy a valid event from a track. /datum/storyteller/proc/buy_event(datum/round_event_control/bought_event, track, forced = FALSE) + if(!track) + track = bought_event.track + var/datum/controller/subsystem/gamemode/mode = SSgamemode // Perhaps use some bell curve instead of a flat variance? var/total_cost = bought_event.cost * mode.point_thresholds[track] @@ -160,8 +176,7 @@ mode.event_track_points[track] = max(mode.event_track_points[track] - total_cost, 0) message_admins("Storyteller purchased and triggered [bought_event] event, on [track] track, for [total_cost] cost.") if(bought_event.roundstart) - if(!ignores_roundstart) - SSgamemode.ran_roundstart = TRUE + SSgamemode.ran_roundstart = TRUE mode.TriggerEvent(bought_event, forced) else mode.schedule_event(bought_event, 3 MINUTES, total_cost, _forced = forced) diff --git a/monkestation/code/modules/storytellers/storytellers/brute.dm b/monkestation/code/modules/storytellers/storytellers/brute.dm new file mode 100644 index 000000000000..838e2ea0f7ff --- /dev/null +++ b/monkestation/code/modules/storytellers/storytellers/brute.dm @@ -0,0 +1,18 @@ +/datum/storyteller/brute + name = "The Brute" + desc = "While the brute will hit hard, it tires quickly." + starting_point_multipliers = list( + EVENT_TRACK_MUNDANE = 1, + EVENT_TRACK_MODERATE = 1, + EVENT_TRACK_MAJOR = 1, + EVENT_TRACK_ROLESET = 1.2, + EVENT_TRACK_OBJECTIVES = 1, + ) + point_gains_multipliers = list( + EVENT_TRACK_MUNDANE = 0.8, + EVENT_TRACK_MODERATE = 0.8, + EVENT_TRACK_MAJOR = 0.8, + EVENT_TRACK_ROLESET = 0.3, + EVENT_TRACK_OBJECTIVES = 1, + ) + weight = 0 diff --git a/monkestation/icons/effects/mouse_pointers/feast.dmi b/monkestation/icons/effects/mouse_pointers/feast.dmi new file mode 100644 index 0000000000000000000000000000000000000000..10d3dc2cba0b10989799697b76f5959926cffffc GIT binary patch literal 370 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=ffJoT`wB5|`BC)1cK9bcFr<;c?I{-Ep3w3R83Pp0KL?3hvXk>QYfbVwVCD*uGC>m(* zK~ERQkcwMxFYo6%s|Y|O$@*GFPzf0_w`Q3A~A^7{&%{n6sKftx&`8T My85}Sb4q9e0K8+G<^TWy literal 0 HcmV?d00001 diff --git a/monkestation/icons/mob/actions/actions_zombie.dmi b/monkestation/icons/mob/actions/actions_zombie.dmi new file mode 100644 index 0000000000000000000000000000000000000000..77a63c3fd71d05a6001103240b747cd1bc2ff4f4 GIT binary patch literal 1925 zcmV;02YUF4P)Eq_=?p{~-{3ITV*imb(BIuuzDq z0D74K%$WfH%$dypX8->)|NogY|1wok^Fuk8>i2KV@ps4$b@h zFD0sRmAbZ_$p8QbGf6~2RA_?t2ak!*A>>UC9##n15h()aeFYuNWJm_yx z8ju#XYP_I3`Yi&N#tI%R0L^wXM;Ia~(56+e05wnM=r;)h_3I$W(MdRm5ymv?Pp;9K zN~K_AtZyaBjN0z@H> z3w{zt)buNj^gTcrbfhu3Ct46FWCg^yZUF(B13;WTS>XsoEMhnU@Cra{$fBJjVyuaP z3jr{+moCLRKmj0a%}0<$hUckRut2epL8L6EDZCKif{VDU1_9O`rfD6ZVw$=LU~xGF zueopsD+MnCaA8}j6g3|KAY8aSFIfl+VCb4-sVJI2%K(*BO;C+mXq*8qd&`Zi_=o^Z zwbiAT11>8-wv?-Rsx=lnwUeh=DOv(@k5v}}+?7;K>wqQ$tYR>~6SXv@@|{*|S=2n_ zmVnC}0Cfi7R)Flyt_2X~T|kO5t>9U|%l92=>ZJk!O?4S$g`161a~rmHAEoK4_2M!Rs3#3Asf_I%ltIo? z7Ew6vT`Bgu)RKZ3u?wc&6nkHA88)R;tYR1ZW^YQ43H@$PI_kL9Q5gE#d5?0j$t`04rJNY0%P4&-{hu5T^Q2dnYjrmU>|x=}Kn4@%>! zWloW)D!;OzfGv%n^FeJ~ z3|#P15Z2;u_xp{m%IAYHt`-0Rj+ydk`;iZj`ST#3_XHp4r(;{#uC5kK$lpJ61N4~pX%fU*=Kj2j|8M%e%!`0=ke z!>Hhv0M+ruGQf?IHZN&6X$lG+Uyn!95ENVkAS{IPc#{F(Fd0wBB@cmyuP;#XKo_{E z;2r_GI<9gH43CAVnx&{>aG2xb#Ag4<6LgH8+?9V$!owot{25F zmby}st`vazP-;?^Zc5QKzTEXOf8qnz!&-O4Nmc>V_@+5+<4QQL)%W{-0+f26FusMi z?Ov8H6x?q6036?a+;$*If9-sL@5g>T zOF)3N=o@S3tGXh<17qxa4}Z_b09-^Z0TJ+^RRDP3v*Gpa`yMX+NcXM(lJ@B61eouwE9I6~vs z$>E!8v02QpwnlKx3vvit)U(qj(33zJzOlB&7^Clp{spm~)AW7)^sEs9#>&b<1AqrN zfsbB2I~ipvMMBVn+4u~yu##aJJi$xP9#-60K>~y|JmKWkVmJD4e|GW{0R^8lAjgye zp0O8V(17!Kgc9s<`0tOVc}xj=P5K(LAYj3txhN5Sb=UtVf2sTj$Eaov)^T;T00000 LNkvXXu0mjfC&O%6 literal 0 HcmV?d00001 diff --git a/monkestation/icons/mob/species/zombie/special_zombie_overlays.dmi b/monkestation/icons/mob/species/zombie/special_zombie_overlays.dmi new file mode 100644 index 0000000000000000000000000000000000000000..502eec04f4ead99089a354f91538616dd0388c93 GIT binary patch literal 2030 zcmYjSdpOgJ8~+Y5VWtUP+)_koa(611S<7u=B#y)57L91AS(0r>ZXv8BVmMYIkq!}# zZCDXT?gy2FwA`lLX4roF{rdGh=b!iUexA?sywCf5-sgSaR2S!Sa=Z8M1^__L-VTWY z0FWpJ0bp?vgjIUFi@?am$^EPdBLKhx0O|nX9stB;dC=;~iZGbvo;}8JIL65d1psPl zYK|fa0LXcmWB_>H-2(k@v1=G-H^6#!G+0DhW8FQkAumM-MqzQ+uvbMT2?aS6^zK#z zC~Blr7tvJ3tG}+LkLim|8g+W)hd#1zjsGO)4-HOGE_tJ8<`U&jc+Ih6+3F7QQeV(l zS*Xg8Xto-v1i}jK%#$1ljoYhs1Yxed6s+Iil;AEM^3L1}X%SY?%)t4^)znF568(uL zDT8d-!I3dJ%e)5cCHMrQI`Hj5F*^(5}XwpzeE!xxOnyo5b;qM{$>1+FUbQroPTC7b{-%0y0bocl8!FXS{dD_-s&D&6fC}eS^D#O)B zfUfDGsS(58(gR_(1{uz-4@(J!>#Xi2e46l01-RSQHt)f~Qo_i}g?nie1bm(G6}vwbMXZi7DvId@WoDRs2feYRi>)#)7{s>~l>x&TW+HD0q+DikOvXlfB$! z{7(s|VE5?$#_|p4qZvr1`4zR&4;8k9F6i~4@VvL?dv>*b)*X*jr6dI(xT&6Ye#sh0 zCx`a;UD=UhJdII+l2)q%WS@xCw7_pI;#t0j`&6lSo6DW}TW@~bq(+}{i&X{{PG(=s zDJ#j(A}DviX|jvGc(O>$mpC0uj!~FCX@y=lkh#xk5lEr3FC5{k(*!)=e@6Tao7xUa z8LM1|GY*+;5$5W+l~jFsC}=EK7pB{9h5mJA$KzV;+Xmv4XP#ZQ0NbC>4eSJ&mPYwL z)QsXu;a9u1j}5RJu$CU1dVPWaI?9o1nrLo?1wk2@(_{vW7w>f*Hg6*t+t z?qlDi(*O4G9SrI8q2PPMeqqn zuib9!F&? z)f*(?Y@aqp7{vEP>N5Rdb{P`xE|{cZ?JSH_%?2)>95b#IRu-={^HO!uzei!P$AZ-K zz>*EeG3EYvN@%82x;QlGTt-}U=r-S+ZaqBtUM@YRFM03U)VI)Y{6K^J`@FTtc&4_^ zRp2!#dJDaj)-TeS zW8=r)%q}Q{N&cpg$%mJAX`XJmPG@ck=qF8QPYe$S+USbWiF{=__J&njG^JUj_`IKr z6IP9S5#io+_`p(T;o8Q_wJpO%$lqQeR^eX@!+8I1;=6JHdVzTFK(E_Z&4T*}gA$Yb z@t^_=%gNKfHAxc!L*;Mpoj?R$Xbol&)k_ZH& z=#{RLI*7OR(fT;evhq@@=5}?j-ioLp6GGg3^|BEurdMOAR(k^0mZghhHTlxzadJ=Amgr zwR~kG20T*%p+$@bw%f*kn%O~Q7*XUU3Uz