mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2026-07-13 00:55:20 +01:00
249 lines
9.1 KiB
Plaintext
249 lines
9.1 KiB
Plaintext
/// Default poly, presumably died the last shift and has no special traits.
|
|
#define POLY_DEFAULT "default"
|
|
/// Poly has survived a number of rounds equivalent to the longest survival of his being.
|
|
#define POLY_LONGEST_SURVIVAL "longest_survival"
|
|
/// Poly has survived a number of rounds equivalent to the longest deathstreak of his being.
|
|
#define POLY_BEATING_DEATHSTREAK "longest_deathstreak"
|
|
/// Poly has only just survived a round, and is doing a consecutive one.
|
|
#define POLY_CONSECUTIVE_ROUND "consecutive_round"
|
|
/// haunt filter we apply to who we possess
|
|
#define POLY_POSSESS_FILTER "poly_possess_filter"
|
|
/// haunt filter color we apply to who we possess
|
|
#define POLY_POSSESS_GLOW "#522059"
|
|
|
|
/// The classically famous compadre to the Chief Engineer, Poly.
|
|
/mob/living/basic/parrot/poly
|
|
name = "Poly"
|
|
desc = "Poly the Parrot. An expert on quantum cracker theory."
|
|
gold_core_spawnable = NO_SPAWN
|
|
speech_probability_rate = 6
|
|
|
|
/// Callback to save our memory at the end of the round.
|
|
var/datum/callback/roundend_callback = null
|
|
/// Did we write the memory to disk?
|
|
var/memory_saved = FALSE
|
|
/// How long has this bird been alive for?
|
|
var/rounds_survived = 0
|
|
/// How long have we survived for at max?
|
|
var/longest_survival = 0
|
|
/// How many rounds in a row have we been dead for?
|
|
var/longest_deathstreak = 0
|
|
|
|
/mob/living/basic/parrot/poly/Initialize(mapload)
|
|
. = ..()
|
|
|
|
if(!memory_saved)
|
|
roundend_callback = CALLBACK(src, PROC_REF(Write_Memory))
|
|
SSticker.OnRoundend(roundend_callback)
|
|
|
|
REGISTER_REQUIRED_MAP_ITEM(1, 1) // every map needs a poly!
|
|
update_appearance()
|
|
|
|
if(!SStts.tts_enabled)
|
|
return
|
|
|
|
voice = SStts.random_tts_voice()
|
|
if(SStts.pitch_enabled)
|
|
if(findtext(voice, "Woman"))
|
|
pitch = 12 // up-pitch by one octave
|
|
else
|
|
pitch = 24 // up-pitch by 2 octaves
|
|
else
|
|
voice_filter = "rubberband=pitch=1.5" // Use the filter to pitch up if we can't naturally pitch up.
|
|
|
|
/mob/living/basic/parrot/poly/Destroy()
|
|
LAZYREMOVE(SSticker.round_end_events, roundend_callback) // we do the memory writing stuff on death, but this is important to yeet as fast as we can if we need to destroy
|
|
roundend_callback = null
|
|
return ..()
|
|
|
|
/mob/living/basic/parrot/poly/death(gibbed)
|
|
if(HAS_TRAIT(src, TRAIT_DONT_WRITE_MEMORY))
|
|
return ..() // Don't read memory either.
|
|
if(!memory_saved)
|
|
Write_Memory(TRUE)
|
|
var/special_status = determine_special_poly()
|
|
if(special_status == POLY_LONGEST_SURVIVAL || special_status == POLY_BEATING_DEATHSTREAK || prob(0.666))
|
|
var/mob/living/basic/parrot/poly/ghost/specter = new(loc)
|
|
if(mind)
|
|
mind.transfer_to(specter)
|
|
else
|
|
specter.PossessByPlayer(key)
|
|
return ..()
|
|
|
|
/mob/living/basic/parrot/poly/get_static_list_of_phrases() // there's only one poly, so there should only be one ongoing list of phrases. i guess
|
|
var/static/list/phrases_to_return = list()
|
|
if(length(phrases_to_return))
|
|
return phrases_to_return
|
|
|
|
phrases_to_return += read_memory() // must come first!!!
|
|
// now add some valuable lines every poly should have
|
|
phrases_to_return += list(
|
|
":e Check the crystal, you chucklefucks!",
|
|
":e OH GOD ITS ABOUT TO DELAMINATE CALL THE SHUTTLE",
|
|
":e WHO TOOK THE DAMN MODSUITS?",
|
|
":e Wire the solars, you lazy bums!",
|
|
"Poly wanna cracker!",
|
|
)
|
|
switch(determine_special_poly())
|
|
if(POLY_DEFAULT)
|
|
phrases_to_return += pick("...alive?", "This isn't parrot heaven!", "I live, I die, I live again!", "The void fades!")
|
|
if(POLY_LONGEST_SURVIVAL)
|
|
phrases_to_return += pick("...[longest_survival].", "The things I've seen!", "I have lived many lives!", "What are you before me?")
|
|
if(POLY_BEATING_DEATHSTREAK)
|
|
phrases_to_return += pick("What are you waiting for!", "Violence breeds violence!", "Blood! Blood!", "Strike me down if you dare!")
|
|
if(POLY_CONSECUTIVE_ROUND)
|
|
phrases_to_return += pick("...again?", "No, It was over!", "Let me out!", "It never ends!")
|
|
|
|
return phrases_to_return
|
|
|
|
/mob/living/basic/parrot/poly/update_desc()
|
|
. = ..()
|
|
switch(determine_special_poly())
|
|
if(POLY_LONGEST_SURVIVAL)
|
|
desc += " Old as sin, and just as loud. Claimed to be [rounds_survived]."
|
|
if(POLY_BEATING_DEATHSTREAK)
|
|
desc += " The squawks of [-rounds_survived] dead parrots ring out in your ears..."
|
|
if(POLY_CONSECUTIVE_ROUND)
|
|
desc += " Over [rounds_survived] shifts without a \"terrible\" \"accident\"!"
|
|
|
|
/mob/living/basic/parrot/poly/update_icon()
|
|
. = ..()
|
|
switch(determine_special_poly())
|
|
if(POLY_LONGEST_SURVIVAL)
|
|
add_atom_colour("#EEEE22", FIXED_COLOUR_PRIORITY)
|
|
if(POLY_BEATING_DEATHSTREAK)
|
|
add_atom_colour("#BB7777", FIXED_COLOUR_PRIORITY)
|
|
|
|
/// Reads the memory of the parrot, and updates the necessary variables. Returns a list of phrases to add to the parrot's speech buffer.
|
|
/mob/living/basic/parrot/poly/proc/read_memory()
|
|
RETURN_TYPE(/list)
|
|
var/list/returnable_list = list()
|
|
if(fexists("data/npc_saves/Poly.sav")) //legacy compatability to convert old format to new
|
|
var/savefile/legacy = new /savefile("data/npc_saves/Poly.sav")
|
|
legacy["phrases"] >> returnable_list
|
|
legacy["roundssurvived"] >> rounds_survived
|
|
legacy["longestsurvival"] >> longest_survival
|
|
legacy["longestdeathstreak"] >> longest_deathstreak
|
|
fdel("data/npc_saves/Poly.sav")
|
|
|
|
else
|
|
var/json_file = file("data/npc_saves/Poly.json")
|
|
if(!fexists(json_file))
|
|
return list()
|
|
var/list/json = json_decode(file2text(json_file))
|
|
returnable_list = json["phrases"]
|
|
rounds_survived = json["roundssurvived"]
|
|
longest_survival = json["longestsurvival"]
|
|
longest_deathstreak = json["longestdeathstreak"]
|
|
|
|
return returnable_list
|
|
|
|
/// Determines the type of Poly we might have here based on the statistics we got from the memory.
|
|
/mob/living/basic/parrot/poly/proc/determine_special_poly()
|
|
if(rounds_survived == longest_survival)
|
|
return POLY_LONGEST_SURVIVAL
|
|
else if(rounds_survived == longest_deathstreak)
|
|
return POLY_BEATING_DEATHSTREAK
|
|
else if(rounds_survived > 0)
|
|
return POLY_CONSECUTIVE_ROUND
|
|
else
|
|
return POLY_DEFAULT
|
|
|
|
/mob/living/basic/parrot/poly/Write_Memory(dead, gibbed)
|
|
. = ..()
|
|
if(!. || memory_saved) // if we die, no more memory
|
|
return FALSE
|
|
|
|
if(!dead && (stat != DEAD))
|
|
dead = FALSE
|
|
|
|
var/file_path = "data/npc_saves/Poly.json"
|
|
var/list/file_data = list()
|
|
|
|
var/list/exportable_speech_buffer = ai_controller.blackboard[BB_EXPORTABLE_STRING_BUFFER_LIST] // should have been populated when we sent the signal out on parent
|
|
if(!!length(exportable_speech_buffer))
|
|
file_data["phrases"] = exportable_speech_buffer
|
|
|
|
if(dead)
|
|
file_data["roundssurvived"] = min(rounds_survived - 1, 0)
|
|
file_data["longestsurvival"] = longest_survival
|
|
if(rounds_survived - 1 < longest_deathstreak)
|
|
file_data["longestdeathstreak"] = rounds_survived - 1
|
|
else
|
|
file_data["longestdeathstreak"] = longest_deathstreak
|
|
else
|
|
|
|
file_data["roundssurvived"] = max(rounds_survived, 0) + 1
|
|
if(rounds_survived + 1 > longest_survival)
|
|
file_data["longestsurvival"] = rounds_survived + 1
|
|
else
|
|
file_data["longestsurvival"] = longest_survival
|
|
file_data["longestdeathstreak"] = longest_deathstreak
|
|
|
|
rustg_file_write(json_encode(file_data, JSON_PRETTY_PRINT), file_path)
|
|
memory_saved = TRUE
|
|
return TRUE
|
|
|
|
/mob/living/basic/parrot/poly/setup_headset()
|
|
ears = new /obj/item/radio/headset/headset_eng(src)
|
|
|
|
/mob/living/basic/parrot/poly/ghost
|
|
name = "The Ghost of Poly"
|
|
desc = "Doomed to squawk the Earth."
|
|
color = "#FFFFFF77"
|
|
sentience_type = SENTIENCE_BOSS //This is so players can't mindswap into ghost poly to become a literal god
|
|
incorporeal_move = INCORPOREAL_MOVE_BASIC
|
|
status_flags = NONE
|
|
butcher_results = list(/obj/item/ectoplasm = 1)
|
|
ai_controller = /datum/ai_controller/basic_controller/parrot/ghost
|
|
speech_probability_rate = 1
|
|
resistance_flags = parent_type::resistance_flags | SHUTTLE_CRUSH_PROOF
|
|
|
|
/mob/living/basic/parrot/poly/ghost/Initialize(mapload)
|
|
// block anything and everything that could possibly happen with writing memory for ghosts
|
|
memory_saved = TRUE
|
|
add_traits(list(TRAIT_GODMODE, TRAIT_DONT_WRITE_MEMORY), INNATE_TRAIT)
|
|
RegisterSignal(src, COMSIG_MOVABLE_MOVED, PROC_REF(on_moved))
|
|
return ..()
|
|
|
|
//we perch on human souls
|
|
/mob/living/basic/parrot/poly/ghost/perch_on_human(mob/living/carbon/human/target)
|
|
if(loc == target) //dismount
|
|
forceMove(get_turf(target))
|
|
return FALSE
|
|
if(ishuman(loc))
|
|
balloon_alert(src, "already possessing!")
|
|
return FALSE
|
|
forceMove(target)
|
|
return TRUE
|
|
|
|
/mob/living/basic/parrot/poly/ghost/proc/on_moved(atom/movable/movable, atom/old_loc)
|
|
SIGNAL_HANDLER
|
|
|
|
if(ishuman(old_loc))
|
|
var/mob/living/unpossessed_human = old_loc
|
|
unpossessed_human.remove_filter(POLY_POSSESS_FILTER)
|
|
return
|
|
|
|
if(!ishuman(loc))
|
|
return
|
|
|
|
var/mob/living/possessed_human = loc
|
|
possessed_human.add_filter(POLY_POSSESS_FILTER, 2, list("type" = "outline", "color" = POLY_POSSESS_GLOW, "size" = 2))
|
|
var/filter = possessed_human.get_filter(POLY_POSSESS_FILTER)
|
|
|
|
if(filter)
|
|
animate(filter, alpha = 200, time = 2 SECONDS, loop = -1)
|
|
animate(alpha = 60, time = 2 SECONDS)
|
|
|
|
var/datum/disease/parrot_possession/on_possession = new /datum/disease/parrot_possession
|
|
on_possession.set_parrot(src)
|
|
possessed_human.ForceContractDisease(on_possession, make_copy = FALSE, del_on_fail = TRUE)
|
|
|
|
#undef POLY_DEFAULT
|
|
#undef POLY_LONGEST_SURVIVAL
|
|
#undef POLY_BEATING_DEATHSTREAK
|
|
#undef POLY_CONSECUTIVE_ROUND
|
|
#undef POLY_POSSESS_FILTER
|
|
#undef POLY_POSSESS_GLOW
|