From 13e85a7399c1873ca0ac4d725acc5c0546e0cbec Mon Sep 17 00:00:00 2001 From: MrMelbert Date: Wed, 17 Jun 2020 11:59:58 -0500 Subject: [PATCH 001/196] Mech battle system!! --- code/game/objects/items/toys.dm | 388 +++++++++++++++++++++++++++++++- 1 file changed, 379 insertions(+), 9 deletions(-) diff --git a/code/game/objects/items/toys.dm b/code/game/objects/items/toys.dm index 497e238460b..5c426e21497 100644 --- a/code/game/objects/items/toys.dm +++ b/code/game/objects/items/toys.dm @@ -26,6 +26,12 @@ * Broken Radio */ +/// Mech battle special attack types. +#define SPECIAL_ATTACK_HEAL 1 +#define SPECIAL_ATTACK_DAMAGE 2 +#define SPECIAL_ATTACK_UTILITY 3 +#define SPECIAL_ATTACK_OTHER 4 + /obj/item/toy throwforce = 0 @@ -525,11 +531,55 @@ /obj/item/toy/prize icon = 'icons/obj/toy.dmi' icon_state = "ripleytoy" + verb_say = "beeps" + verb_ask = "beeps" + verb_exclaim = "beeps" + verb_yell = "beeps" + /// Timer when it'll be off cooldown var/timer = 0 - var/cooldown = 30 - var/quiet = 0 + /// Cooldown between play sessions - 4x longer after a battle + var/cooldown = 25 + /// If it makes noise when played with + var/quiet = FALSE + /// TRUE = Offering battle to someone || FALSE = Not offering battle + var/wants_to_battle = FALSE + /// TRUE = in combat currently || FALSE = Not in combat + var/in_combat = FALSE + /// The mech's health in battle + var/combat_health = 0 + /// The mech's max combat health + var/max_combat_health = 0 + /// TRUE = the special attack is charged || FALSE = not charged + var/special_attack_charged = FALSE + /// What type of special attack they use - SPECIAL_ATTACK_DAMAGE, SPECIAL_ATTACK_HEAL, SPECIAL_ATTACK_UTILITY, SPECIAL_ATTACK_OTHER + var/special_attack_type = 0 + /// What message their special move gets on examining + var/special_attack_type_message = "a mystery move." + /// The battlecry when using the special attack + var/special_attack_cry = "*flip" + /// Current cooldown of their special attack + var/special_attack_cooldown = 0 + /// This mech's win count in combat + var/wins = 0 + /// ...And their loss count in combat + var/losses = 0 w_class = WEIGHT_CLASS_SMALL +/obj/item/toy/prize/Initialize() + . = ..() + max_combat_health = combat_health + switch(special_attack_type) + if(SPECIAL_ATTACK_DAMAGE) + special_attack_type_message = "an aggressive move, which deals bonus damage." + if(SPECIAL_ATTACK_HEAL) + special_attack_type_message = "a defensive move, which grants bonus healing." + if(SPECIAL_ATTACK_UTILITY) + special_attack_type_message = "a utility move, which heals the user and damages the opponent." + if(SPECIAL_ATTACK_OTHER) + special_attack_type_message = "a special move, which [special_attack_type_message]" + else + special_attack_type_message = "a mystery move, even I don't know." + //all credit to skasi for toy mech fun ideas /obj/item/toy/prize/attack_self(mob/user) if(timer < world.time) @@ -540,77 +590,392 @@ else . = ..() -/obj/item/toy/prize/attack_hand(mob/user) +/obj/item/toy/prize/attackby(obj/item/I, mob/living/user) + if(istype(I, /obj/item/toy/prize)) + if(!in_combat) + mechaBrawl(I, user) + else + to_chat(user, "[name] is already in combat!") + ..() + +/obj/item/toy/prize/attack(mob/living/carbon/human/target, mob/living/carbon/human/user) + for(var/obj/item/toy/prize/I in target.held_items) + if(istype(I, /obj/item/toy/prize)) + if(wants_to_battle) + to_chat(user, "You already are offering battle!") + break + if(I.wants_to_battle) //if the target mech wants to battle, initiate the batte from their POV + I.mechaBrawl(src, user, target) + I.wants_to_battle = FALSE + else //extend the offer of battle to the other mech + to_chat(user, "You offer battle to [target.name] and [target.p_their()] [I.name]!") + to_chat(target, "[user.name] wants to battle with your [I.name]! Attack them with your mech to initiate combat.") + wants_to_battle = TRUE + addtimer(CALLBACK(src, .proc/withdrawOffer, user), 60) + break + ..() + +/** + * Resets the request for battle. + * + * For use in a timer, this proc resets the wants_to_battle variable after a short period. + * Arguments + * user: the user wanting to do battle + */ +/obj/item/toy/prize/proc/withdrawOffer(mob/living/user) + wants_to_battle = FALSE + to_chat(user, "You get the feeling they don't want to battle.") + +/obj/item/toy/prize/examine() . = ..() - if(.) + . += "This toy's special attack is [special_attack_cry], [special_attack_type_message] " + if(in_combat) + . += "DEBUG: Current health is [combat_health]" + switch(combat_health) + if(-1 to round(max_combat_health*0.25)-1) + . += "It looks devastated, and not emotionally!" + if(round(max_combat_health*0.25) to round(max_combat_health*0.5)-1) + . += "It looks increasingly worn out!" + if(round(max_combat_health*0.5) to round(max_combat_health*0.75)-1) + . += "It seems to be faltering!" + if(round(max_combat_health*0.75) to INFINITY) + . += "It appears completely healthy and ready to brawl." + else + . += "It's just vibing." + + if(special_attack_cooldown == 0) + . += "Its special move light is flashing green." + else + . += "Its special move light is red." + + else if(wins || losses) + . += "This toy has [wins] wins, and [losses] losses." + +/** + * The 'master' proc of the mech battle. Processes the entire battle's events and makes sure it start and finishes correctly. + * + * After going through a few checks at the beginning to ensure the battle can start properly, the battle begins a loop that lasts + * until either toy has no more health. During this loop, it also ensures the mechs stay in combat range of each other. + * It will then randomly decide attacks for each toy, occasionally making one or the other use their special attack. + * When either mech has no more health, the loop ends, and it displays the victor and the loser while updating their stats and resetting them. + * + * Arguments + * + * src: the defending toy, the toy you hit another toy with to start the battle (not an arg but relevant / makes it easier to read if you understand) + * attacker: the attacking toy, the toy you target with another toy to start the battle. in PvP, this is the opponent's toy + * referee: the user, the one who is holding the toys / controlling the fight + * opponent: optional arg used in Mech PvP battles: the other person who is taking part in the fight + */ +/obj/item/toy/prize/proc/mechaBrawl(obj/item/toy/prize/attacker, mob/living/carbon/referee, mob/living/opponent) + if(src.in_combat || attacker.in_combat) return - if(loc == user) - attack_self(user) + + if(timer < world.time) + timer = world.time + cooldown*4 + else + to_chat(referee, "[name] isn't ready for battle.") + return + + if(attacker.timer < world.time) + attacker.timer = world.time + cooldown*4 + else + to_chat(referee, "[attacker.name] isn't ready for battle.") + opponent?.to_chat(opponent, "[attacker.name] isn't ready for battle.") + return + + referee.visible_message(" [referee.name] collides [attacker.name] with [name]! Looks like they're preparing for a brawl! ", \ + " You collide [attacker.name] into [name], sparking a fierce battle! ", \ + " You hear hard plastic smacking into hard plastic.", COMBAT_MESSAGE_RANGE) + + /// Who's in control of the attacker? + var/mob/living/attacker_controller = (opponent)? opponent : referee + + src.in_combat = TRUE + attacker.in_combat = TRUE + + sleep(15) + while(combat_health > 0 && attacker.combat_health > 0) + + //if the two toys aren't next to each other, the battle ends + if(!in_range(src, attacker)) + referee.visible_message(" [attacker.name] and [name] separate, ending the battle. ", \ + " [attacker.name] and [name] separate, ending the battle. ") + break + //if the referee isn't next either both toys (and doesn't have telekinesis), the battle ends + if((!in_range(src, referee) || !in_range(attacker, referee)) && !(referee.dna.check_mutation(TK))) + referee.visible_message(" [referee.name] seperates from [name] and [attacker.name], ending the battle.", \ + " You separate [attacker.name] and [name], ending the battle. ") + break + //if it's PVP and the opponent is not next to the attacking toy (and doesn't have telekinesis), the battle ends + if(opponent && !in_range(attacker, opponent) && !(referee.dna.check_mutation(TK))) + opponent.visible_message(" [referee.name] seperates from [attacker.name], ending the battle.", \ + " You separate from [attacker.name], ending the battle. ") + break + + if(special_attack_charged) + specialAttackMove(attacker) + else if(attacker.special_attack_charged) + attacker.specialAttackMove(src) + else + if(special_attack_cooldown > 0) + special_attack_cooldown-- + if(attacker.special_attack_cooldown > 0) + attacker.special_attack_cooldown-- + + switch(rand(1,6)) + if(1 to 2) //attacker wins + if(attacker.combat_health <= round(attacker.max_combat_health/3) && special_attack_cooldown == 0) //if health is less than 1/3 and special off CD, use it + attacker.special_attack_charged = TRUE + attacker_controller.visible_message(" [attacker.name] begins charging his special attack!! ", \ + " You begin charging [attacker.name]\'s special attack! ") + else //just attack + attacker.SpinAnimation(5, 0) + combat_health-- + attacker_controller.visible_message(" [attacker.name] devastates [name]! ", \ + " You ram [attacker.name] into [name]! ", \ + " You hear hard plastic smacking hard plastic.", COMBAT_MESSAGE_RANGE) + if(prob(5)) + combat_health-- + attacker_controller.visible_message(" ...and lands a CRIPPLING BLOW! ", \ + " ...and you land a CRIPPLING blow on [name]! ", null, COMBAT_MESSAGE_RANGE) + + if(3) //both lose + attacker.SpinAnimation(5, 0) + SpinAnimation(5, 0) + combat_health-- + attacker.combat_health-- + if(prob(50)) + referee.visible_message(" [attacker.name] and [name] clash dramatically, causing sparks to fly! ", \ + " [attacker.name] and [name] clash dramatically, causing sparks to fly! ", \ + " You hear hard plastic rubbing against hard plastic.", COMBAT_MESSAGE_RANGE) + else + attacker_controller.visible_message(" [name] and [attacker.name] clash dramatically, causing sparks to fly! ", \ + " [name] and [attacker.name] clash dramatically, causing sparks to fly! ", \ + " You hear hard plastic rubbing against hard plastic.", COMBAT_MESSAGE_RANGE) + if(4) //both win + if(prob(50)) + referee.visible_message(" [name]'s attack deflects off of [attacker.name]. ", \ + " [name]'s attack deflects off of [attacker.name]. ", \ + " You hear hard plastic bouncing off hard plastic.", COMBAT_MESSAGE_RANGE) + else + attacker_controller.visible_message(" [attacker.name]'s attack deflects off of [name]. ", \ + " [attacker.name]'s attack deflects off of [name]. ", \ + " You hear hard plastic bouncing off hard plastic.", COMBAT_MESSAGE_RANGE) + + if(5 to 6) //defender wins + if(combat_health <= round(max_combat_health/3) && special_attack_cooldown == 0) //if health is less than 1/3 and special off CD, use it + special_attack_charged = TRUE + referee.visible_message(" [name] begins charging his special attack!! ", \ + " You begin charging [name]\'s special attack! ") + else //just attack + SpinAnimation(5, 0) + attacker.combat_health-- + referee.visible_message(" [name] smashes [attacker.name]! ", \ + " You smash [name] into [attacker.name]! ", \ + " You hear hard plastic smashing hard plastic.", COMBAT_MESSAGE_RANGE) + if(prob(5)) + attacker.combat_health-- + referee.visible_message(" ...and lands a CRIPPLING BLOW! ", \ + " ...and you land a CRIPPLING blow on [attacker.name]! ", null, COMBAT_MESSAGE_RANGE) + else + referee.visible_message(" [name] and [attacker.name] stand around awkwardly.", \ + " You don't know what to do next.") + sleep(10) + + if(attacker.combat_health <= 0 && combat_health <= 0) //both lose + referee.visible_message(" MUTUALLY ASSURED DESTRUCTION!! [name] and [attacker.name] both end up destroyed!", \ + " Both [name] and [attacker.name] are destroyed!") + else if(attacker.combat_health <= 0) //src wins + wins++ + attacker.losses++ + attacker_controller.visible_message(" [attacker.name] falls apart!", \ + " [attacker.name] falls apart!", null, COMBAT_MESSAGE_RANGE) + if(!quiet) + say("YOU SHOULDN'T HAVE ATTACKED ME") + referee.visible_message(" [name] destroys [attacker.name] and walks away victorious!", \ + " You raise up [name] victoriously over [attacker.name]!") + else if (combat_health <= 0) //attacker wins + attacker.wins++ + losses++ + referee.visible_message(" [name] collapses!", \ + " [name] collapses!", null, COMBAT_MESSAGE_RANGE) + if(!attacker.quiet) + attacker.say("EASY FIGHT") + attacker_controller.visible_message(" [attacker.name] demolishes [name] and walks away victorious!", \ + " You raise up [attacker.name] proudly over [name]!") + else //both win? + (!quiet)? say("I WENT EASY ON YOU"):null + (!attacker.quiet)? attacker.say("OF COURSE YOU DID"):null + + src.in_combat = FALSE + attacker.in_combat = FALSE + + combat_health = max_combat_health + attacker.combat_health = attacker.max_combat_health + + return + +/** + * Processes any special attack moves that happen in the battle (called in the mechaBattle proc). + * + * Makes the toy shout their special attack cry and updates its cooldown. Then, does the special attack. + * Arguments + * + * src: the toy, doing its special move(not an arg but relevant) + * victim: the toy being hit by the special move + */ +/obj/item/toy/prize/proc/specialAttackMove(obj/item/toy/prize/victim) + if(!quiet) + say(special_attack_cry + "!!") + + special_attack_charged = FALSE + special_attack_cooldown = 3 + + switch(special_attack_type) + if(SPECIAL_ATTACK_DAMAGE) //+2 damage + victim.combat_health-=2 + if(SPECIAL_ATTACK_HEAL) //+2 healing + combat_health+=2 + if(SPECIAL_ATTACK_UTILITY) //+1 heal, +1 damage + victim.combat_health-- + combat_health++ + if(SPECIAL_ATTACK_OTHER) //other + superSpecialAttack(victim) + else + say("I FORGOT MY SPECIAL ATTACK...") + +/** + * Base proc for 'other' special attack moves. + * + * This one is only for inheritance, each mech with an 'other' type move has their procs below. + * Arguments + * src: the toy, doing its super special move(not an arg but relevant) + * victim: the toy being hit by the super special move (doesn't necessarily need to be used) + */ +/obj/item/toy/prize/proc/superSpecialAttack(obj/item/toy/prize/victim) + visible_message(" [name] does a cool flip.") /obj/item/toy/prize/ripley name = "toy Ripley" desc = "Mini-Mecha action figure! Collect them all! 1/13." + combat_health = 3 + special_attack_type = SPECIAL_ATTACK_DAMAGE + special_attack_cry = "OMEGA DRILL" /obj/item/toy/prize/fireripley - name = "toy firefighting Ripley" + name = "toy Firefighting Ripley" desc = "Mini-Mecha action figure! Collect them all! 2/13." icon_state = "fireripleytoy" + combat_health = 4 + special_attack_type = SPECIAL_ATTACK_UTILITY + special_attack_cry = "FIRE SHIELD" /obj/item/toy/prize/deathripley - name = "toy deathsquad Ripley" + name = "toy Deathsquad Ripley" desc = "Mini-Mecha action figure! Collect them all! 3/13." icon_state = "deathripleytoy" + combat_health = 5 + special_attack_type = SPECIAL_ATTACK_OTHER + special_attack_type_message = "instantly destroys the opposing mech if its health is less than this mech's health." + special_attack_cry = "KILLER CLAMP" + +/obj/item/toy/prize/deathripley/superSpecialAttack(obj/item/toy/prize/victim) + if(victim.combat_health < combat_health) //Instantly kills the other mech if it's health is below our's. + say("EXECUTE!!") + victim.combat_health = 0 + else //Otherwise, just deal one damage. + victim.combat_health-- /obj/item/toy/prize/gygax name = "toy Gygax" desc = "Mini-Mecha action figure! Collect them all! 4/13." icon_state = "gygaxtoy" + combat_health = 5 + special_attack_type = SPECIAL_ATTACK_UTILITY + special_attack_cry = "SUPER SERVOS" /obj/item/toy/prize/durand name = "toy Durand" desc = "Mini-Mecha action figure! Collect them all! 5/13." icon_state = "durandtoy" + combat_health = 6 + special_attack_type = SPECIAL_ATTACK_HEAL + special_attack_cry = "SHIELD OF PROTECTION" /obj/item/toy/prize/honk name = "toy H.O.N.K." desc = "Mini-Mecha action figure! Collect them all! 6/13." icon_state = "honktoy" + combat_health = 4 + special_attack_type = SPECIAL_ATTACK_OTHER + special_attack_type_message = "puts the opposing mech's special move on cooldown and heals this mech." + special_attack_cry = "MEGA HORN" + +/obj/item/toy/prize/honk/superSpecialAttack(obj/item/toy/prize/victim) + victim.special_attack_cooldown = 3 //Adds cooldown to the other mech and gives a minor self heal + combat_health++ /obj/item/toy/prize/marauder name = "toy Marauder" desc = "Mini-Mecha action figure! Collect them all! 7/13." icon_state = "maraudertoy" + combat_health = 7 + special_attack_type = SPECIAL_ATTACK_DAMAGE + special_attack_cry = "BEAM BLAST" /obj/item/toy/prize/seraph name = "toy Seraph" desc = "Mini-Mecha action figure! Collect them all! 8/13." icon_state = "seraphtoy" + combat_health = 8 + special_attack_type = SPECIAL_ATTACK_DAMAGE + special_attack_cry = "ROCKET BARRAGE" /obj/item/toy/prize/mauler name = "toy Mauler" desc = "Mini-Mecha action figure! Collect them all! 9/13." icon_state = "maulertoy" + combat_health = 8 + special_attack_type = SPECIAL_ATTACK_DAMAGE + special_attack_cry = "BULLET STORM" /obj/item/toy/prize/odysseus name = "toy Odysseus" desc = "Mini-Mecha action figure! Collect them all! 10/13." icon_state = "odysseustoy" + combat_health = 4 + special_attack_type = SPECIAL_ATTACK_HEAL + special_attack_cry = "MECHA BEAM" /obj/item/toy/prize/phazon name = "toy Phazon" desc = "Mini-Mecha action figure! Collect them all! 11/13." icon_state = "phazontoy" + combat_health = 6 + special_attack_type = SPECIAL_ATTACK_UTILITY + special_attack_cry = "NO-CLIP" /obj/item/toy/prize/reticence name = "toy Reticence" desc = "Mini-Mecha action figure! Collect them all! 12/13." icon_state = "reticencetoy" - quiet = 1 + quiet = TRUE + combat_health = 4 + special_attack_type = SPECIAL_ATTACK_OTHER + special_attack_type_message = "has a lower cooldown than normal special moves and deals damage." + special_attack_cry = "*wave" + +/obj/item/toy/prize/reticence/superSpecialAttack(obj/item/toy/prize/victim) + special_attack_cooldown = 1 //Has a very low cooldown and does minor damage. + victim.combat_health-- /obj/item/toy/prize/clarke name = "toy Clarke" desc = "Mini-Mecha action figure! Collect them all! 13/13." icon_state = "clarketoy" + combat_health = 4 + special_attack_type = SPECIAL_ATTACK_UTILITY + special_attack_cry = "ROLL OUT" /obj/item/toy/talking name = "talking action figure" @@ -1569,3 +1934,8 @@ cooldown = (world.time + 10) sleep(5) playsound(src, 'sound/effects/blobattack.ogg', 50, FALSE) + +#undef SPECIAL_ATTACK_HEAL +#undef SPECIAL_ATTACK_DAMAGE +#undef SPECIAL_ATTACK_UTILITY +#undef SPECIAL_ATTACK_OTHER From 074bb07f9807128bbb398c2c7f3a91bca74ebc70 Mon Sep 17 00:00:00 2001 From: MrMelbert Date: Wed, 17 Jun 2020 18:41:41 -0500 Subject: [PATCH 002/196] suicide_act --- code/game/objects/items/toys.dm | 44 ++++++++++++++++++++++++++++++--- 1 file changed, 40 insertions(+), 4 deletions(-) diff --git a/code/game/objects/items/toys.dm b/code/game/objects/items/toys.dm index 5c426e21497..4b90711152a 100644 --- a/code/game/objects/items/toys.dm +++ b/code/game/objects/items/toys.dm @@ -526,7 +526,7 @@ /* - * Mech prizes + * Mech prizes + MECHA COMBAT!! */ /obj/item/toy/prize icon = 'icons/obj/toy.dmi' @@ -626,6 +626,41 @@ wants_to_battle = FALSE to_chat(user, "You get the feeling they don't want to battle.") +/obj/item/toy/prize/suicide_act(mob/user) + user.visible_message("[user] begins a fight with [name]! There's no way they can win, it looks like [user.p_theyre()] trying to commit suicide!") + + in_combat = TRUE + sleep(20) + SpinAnimation(5, 0) + user.adjustBruteLoss(20) + sleep(10) + user.SpinAnimation(5, 0) + sleep(10) + SpinAnimation(5, 0) + user.adjustBruteLoss(15) + sleep(10) + SpinAnimation(5, 0) + user.adjustBruteLoss(15) + sleep(10) + if(!quiet) + say(special_attack_cry + "!!") + user.emote("scream") + else + SpinAnimation(5, 0) + sleep(15) + if(!in_range(src, user)) + say("PATHETIC") + return SHAME + + user.adjustBruteLoss(450) + in_combat = FALSE + say("AN EASY WIN. MY POWER INCREASES WITH THIS OFFERING.") + add_atom_colour(rgb(255, 115, 115), ADMIN_COLOUR_PRIORITY) + max_combat_health = round(max_combat_health*1.5) + wins++ + return BRUTELOSS + + /obj/item/toy/prize/examine() . = ..() . += "This toy's special attack is [special_attack_cry], [special_attack_type_message] " @@ -694,7 +729,8 @@ attacker.in_combat = TRUE sleep(15) - while(combat_health > 0 && attacker.combat_health > 0) + //--THE BATTLE BEGINS-- + while(combat_health > 0 && attacker.combat_health > 0) //if the two toys aren't next to each other, the battle ends if(!in_range(src, attacker)) @@ -727,7 +763,7 @@ if(attacker.combat_health <= round(attacker.max_combat_health/3) && special_attack_cooldown == 0) //if health is less than 1/3 and special off CD, use it attacker.special_attack_charged = TRUE attacker_controller.visible_message(" [attacker.name] begins charging his special attack!! ", \ - " You begin charging [attacker.name]\'s special attack! ") + " You begin charging [attacker.name]'s special attack! ") else //just attack attacker.SpinAnimation(5, 0) combat_health-- @@ -766,7 +802,7 @@ if(combat_health <= round(max_combat_health/3) && special_attack_cooldown == 0) //if health is less than 1/3 and special off CD, use it special_attack_charged = TRUE referee.visible_message(" [name] begins charging his special attack!! ", \ - " You begin charging [name]\'s special attack! ") + " You begin charging [name]'s special attack! ") else //just attack SpinAnimation(5, 0) attacker.combat_health-- From 1efd7c53b7749ddcc24a5f1ee146ec5279d9fbdf Mon Sep 17 00:00:00 2001 From: MrMelbert Date: Thu, 18 Jun 2020 03:46:43 -0500 Subject: [PATCH 003/196] sounds --- code/game/objects/items/toys.dm | 52 ++++++++++++++++++--------------- 1 file changed, 28 insertions(+), 24 deletions(-) diff --git a/code/game/objects/items/toys.dm b/code/game/objects/items/toys.dm index 4b90711152a..f7dd650f1e7 100644 --- a/code/game/objects/items/toys.dm +++ b/code/game/objects/items/toys.dm @@ -537,7 +537,7 @@ verb_yell = "beeps" /// Timer when it'll be off cooldown var/timer = 0 - /// Cooldown between play sessions - 4x longer after a battle + /// Cooldown between play sessions - 8x longer after a battle var/cooldown = 25 /// If it makes noise when played with var/quiet = FALSE @@ -622,11 +622,11 @@ * Arguments * user: the user wanting to do battle */ -/obj/item/toy/prize/proc/withdrawOffer(mob/living/user) +/obj/item/toy/prize/proc/withdrawOffer(mob/living/carbon/user) wants_to_battle = FALSE to_chat(user, "You get the feeling they don't want to battle.") -/obj/item/toy/prize/suicide_act(mob/user) +/obj/item/toy/prize/suicide_act(mob/living/carbon/user) user.visible_message("[user] begins a fight with [name]! There's no way they can win, it looks like [user.p_theyre()] trying to commit suicide!") in_combat = TRUE @@ -660,30 +660,19 @@ wins++ return BRUTELOSS - /obj/item/toy/prize/examine() . = ..() . += "This toy's special attack is [special_attack_cry], [special_attack_type_message] " if(in_combat) - . += "DEBUG: Current health is [combat_health]" - switch(combat_health) - if(-1 to round(max_combat_health*0.25)-1) - . += "It looks devastated, and not emotionally!" - if(round(max_combat_health*0.25) to round(max_combat_health*0.5)-1) - . += "It looks increasingly worn out!" - if(round(max_combat_health*0.5) to round(max_combat_health*0.75)-1) - . += "It seems to be faltering!" - if(round(max_combat_health*0.75) to INFINITY) - . += "It appears completely healthy and ready to brawl." - else - . += "It's just vibing." - + . += "This toy's got a maximum health of [max_combat_health]. Currently, it's [combat_health]." if(special_attack_cooldown == 0) - . += "Its special move light is flashing green." + . += "Its special move light is green and is ready!" else - . += "Its special move light is red." + . += "Its special move light is flashing red." + else + . += "This toy's got a maximum health of [max_combat_health]." - else if(wins || losses) + if(wins || losses) . += "This toy has [wins] wins, and [losses] losses." /** @@ -696,8 +685,8 @@ * * Arguments * - * src: the defending toy, the toy you hit another toy with to start the battle (not an arg but relevant / makes it easier to read if you understand) - * attacker: the attacking toy, the toy you target with another toy to start the battle. in PvP, this is the opponent's toy + * src: the defending toy, the toy that is hit by another toy with to start the battle (not an 'arg' but relevant / makes it easier to read if you understand) + * attacker: the attacking toy, the toy you use on another toy to start the battle. in PvP, this is the opponent's toy * referee: the user, the one who is holding the toys / controlling the fight * opponent: optional arg used in Mech PvP battles: the other person who is taking part in the fight */ @@ -706,13 +695,13 @@ return if(timer < world.time) - timer = world.time + cooldown*4 + timer = world.time + cooldown*8 else to_chat(referee, "[name] isn't ready for battle.") return if(attacker.timer < world.time) - attacker.timer = world.time + cooldown*4 + attacker.timer = world.time + cooldown*8 else to_chat(referee, "[attacker.name] isn't ready for battle.") opponent?.to_chat(opponent, "[attacker.name] isn't ready for battle.") @@ -766,12 +755,14 @@ " You begin charging [attacker.name]'s special attack! ") else //just attack attacker.SpinAnimation(5, 0) + playsound(attacker, 'sound/mecha/mechstep.ogg', 20, TRUE) combat_health-- attacker_controller.visible_message(" [attacker.name] devastates [name]! ", \ " You ram [attacker.name] into [name]! ", \ " You hear hard plastic smacking hard plastic.", COMBAT_MESSAGE_RANGE) if(prob(5)) combat_health-- + playsound(src, 'sound/effects/meteorimpact.ogg', 20, TRUE) attacker_controller.visible_message(" ...and lands a CRIPPLING BLOW! ", \ " ...and you land a CRIPPLING blow on [name]! ", null, COMBAT_MESSAGE_RANGE) @@ -780,6 +771,8 @@ SpinAnimation(5, 0) combat_health-- attacker.combat_health-- + do_sparks(3, FALSE, src) + do_sparks(3, FALSE, attacker) if(prob(50)) referee.visible_message(" [attacker.name] and [name] clash dramatically, causing sparks to fly! ", \ " [attacker.name] and [name] clash dramatically, causing sparks to fly! ", \ @@ -789,6 +782,7 @@ " [name] and [attacker.name] clash dramatically, causing sparks to fly! ", \ " You hear hard plastic rubbing against hard plastic.", COMBAT_MESSAGE_RANGE) if(4) //both win + playsound(attacker, 'sound/weapons/parry.ogg', 20, TRUE) if(prob(50)) referee.visible_message(" [name]'s attack deflects off of [attacker.name]. ", \ " [name]'s attack deflects off of [attacker.name]. ", \ @@ -805,12 +799,14 @@ " You begin charging [name]'s special attack! ") else //just attack SpinAnimation(5, 0) + playsound(src, 'sound/mecha/mechstep.ogg', 20, TRUE) attacker.combat_health-- referee.visible_message(" [name] smashes [attacker.name]! ", \ " You smash [name] into [attacker.name]! ", \ " You hear hard plastic smashing hard plastic.", COMBAT_MESSAGE_RANGE) if(prob(5)) attacker.combat_health-- + playsound(attacker, 'sound/effects/meteorimpact.ogg', 20, TRUE) referee.visible_message(" ...and lands a CRIPPLING BLOW! ", \ " ...and you land a CRIPPLING blow on [attacker.name]! ", null, COMBAT_MESSAGE_RANGE) else @@ -819,11 +815,13 @@ sleep(10) if(attacker.combat_health <= 0 && combat_health <= 0) //both lose + playsound(src, 'sound/machines/warning-buzzer.ogg', 20, TRUE) referee.visible_message(" MUTUALLY ASSURED DESTRUCTION!! [name] and [attacker.name] both end up destroyed!", \ " Both [name] and [attacker.name] are destroyed!") else if(attacker.combat_health <= 0) //src wins wins++ attacker.losses++ + playsound(attacker, 'sound/effects/light_flicker.ogg', 20, TRUE) attacker_controller.visible_message(" [attacker.name] falls apart!", \ " [attacker.name] falls apart!", null, COMBAT_MESSAGE_RANGE) if(!quiet) @@ -833,6 +831,7 @@ else if (combat_health <= 0) //attacker wins attacker.wins++ losses++ + playsound(src, 'sound/effects/light_flicker.ogg', 20, TRUE) referee.visible_message(" [name] collapses!", \ " [name] collapses!", null, COMBAT_MESSAGE_RANGE) if(!attacker.quiet) @@ -870,11 +869,14 @@ switch(special_attack_type) if(SPECIAL_ATTACK_DAMAGE) //+2 damage victim.combat_health-=2 + playsound(src, 'sound/weapons/marauder.ogg', 20, TRUE) if(SPECIAL_ATTACK_HEAL) //+2 healing combat_health+=2 + playsound(src, 'sound/mecha/mech_shield_raise.ogg', 20, TRUE) if(SPECIAL_ATTACK_UTILITY) //+1 heal, +1 damage victim.combat_health-- combat_health++ + playsound(src, 'sound/mecha/mechmove01.ogg', 20, TRUE) if(SPECIAL_ATTACK_OTHER) //other superSpecialAttack(victim) else @@ -916,6 +918,7 @@ special_attack_cry = "KILLER CLAMP" /obj/item/toy/prize/deathripley/superSpecialAttack(obj/item/toy/prize/victim) + playsound(src, 'sound/weapons/sonic_jackhammer.ogg', 20, TRUE) if(victim.combat_health < combat_health) //Instantly kills the other mech if it's health is below our's. say("EXECUTE!!") victim.combat_health = 0 @@ -948,6 +951,7 @@ special_attack_cry = "MEGA HORN" /obj/item/toy/prize/honk/superSpecialAttack(obj/item/toy/prize/victim) + playsound(src, 'sound/machines/honkbot_evil_laugh.ogg', 20, TRUE) victim.special_attack_cooldown = 3 //Adds cooldown to the other mech and gives a minor self heal combat_health++ From e3375d401ef583a14a47dd0e988a85863d902378 Mon Sep 17 00:00:00 2001 From: MrMelbert Date: Thu, 18 Jun 2020 14:11:57 -0500 Subject: [PATCH 004/196] a few checks here and there + comments --- code/game/objects/items/toys.dm | 169 +++++++++++++++++++++----------- 1 file changed, 111 insertions(+), 58 deletions(-) diff --git a/code/game/objects/items/toys.dm b/code/game/objects/items/toys.dm index f7dd650f1e7..9cab426bbbc 100644 --- a/code/game/objects/items/toys.dm +++ b/code/game/objects/items/toys.dm @@ -554,7 +554,7 @@ /// What type of special attack they use - SPECIAL_ATTACK_DAMAGE, SPECIAL_ATTACK_HEAL, SPECIAL_ATTACK_UTILITY, SPECIAL_ATTACK_OTHER var/special_attack_type = 0 /// What message their special move gets on examining - var/special_attack_type_message = "a mystery move." + var/special_attack_type_message = "" /// The battlecry when using the special attack var/special_attack_cry = "*flip" /// Current cooldown of their special attack @@ -591,28 +591,34 @@ . = ..() /obj/item/toy/prize/attackby(obj/item/I, mob/living/user) - if(istype(I, /obj/item/toy/prize)) - if(!in_combat) - mechaBrawl(I, user) - else - to_chat(user, "[name] is already in combat!") + if(istype(I, /obj/item/toy/prize)) //if you attack a mech with a mech, initiate combat between them + var/obj/item/toy/prize/P = I + if(checkBattleStart(P, user)) + mechaBrawl(P, user) ..() /obj/item/toy/prize/attack(mob/living/carbon/human/target, mob/living/carbon/human/user) for(var/obj/item/toy/prize/I in target.held_items) - if(istype(I, /obj/item/toy/prize)) - if(wants_to_battle) + if(istype(I, /obj/item/toy/prize)) //if you attack someone with a mech who's also holding a mech, offer to battle them + var/obj/item/toy/prize/P = I + + if(!checkBattleStart(P, user, target)) + break + + //slap them with the metaphorical white glove + if(wants_to_battle) //prevent spamming someone with offers to_chat(user, "You already are offering battle!") break - if(I.wants_to_battle) //if the target mech wants to battle, initiate the batte from their POV - I.mechaBrawl(src, user, target) - I.wants_to_battle = FALSE + if(P.wants_to_battle) //if the target mech wants to battle, initiate the battle from their POV + P.mechaBrawl(src, user, target) //P = challenger's mech / SRC = defender's mech / user = defender / target = challenger + P.wants_to_battle = FALSE + return else //extend the offer of battle to the other mech - to_chat(user, "You offer battle to [target.name] and [target.p_their()] [I.name]!") - to_chat(target, "[user.name] wants to battle with your [I.name]! Attack them with your mech to initiate combat.") + to_chat(user, "You offer battle to [target.name] and [target.p_their()] [P.name]!") + to_chat(target, "[user.name] wants to battle with [target.p_their()] [name]! Attack them with your [P.name] to initiate combat.") wants_to_battle = TRUE addtimer(CALLBACK(src, .proc/withdrawOffer, user), 60) - break + ..() /** @@ -627,20 +633,28 @@ to_chat(user, "You get the feeling they don't want to battle.") /obj/item/toy/prize/suicide_act(mob/living/carbon/user) + if(in_combat) + to_chat(user, "[name] is in battle, let it finish first.") + return + user.visible_message("[user] begins a fight with [name]! There's no way they can win, it looks like [user.p_theyre()] trying to commit suicide!") in_combat = TRUE sleep(20) SpinAnimation(5, 0) user.adjustBruteLoss(20) + user.adjustStaminaLoss(40) sleep(10) user.SpinAnimation(5, 0) + combat_health-- //we scratched it! sleep(10) SpinAnimation(5, 0) user.adjustBruteLoss(15) + user.adjustStaminaLoss(30) sleep(10) SpinAnimation(5, 0) user.adjustBruteLoss(15) + user.adjustStaminaLoss(30) sleep(10) if(!quiet) say(special_attack_cry + "!!") @@ -657,6 +671,7 @@ say("AN EASY WIN. MY POWER INCREASES WITH THIS OFFERING.") add_atom_colour(rgb(255, 115, 115), ADMIN_COLOUR_PRIORITY) max_combat_health = round(max_combat_health*1.5) + combat_health = max_combat_health wins++ return BRUTELOSS @@ -664,7 +679,7 @@ . = ..() . += "This toy's special attack is [special_attack_cry], [special_attack_type_message] " if(in_combat) - . += "This toy's got a maximum health of [max_combat_health]. Currently, it's [combat_health]." + . += "This toy has a maximum health of [max_combat_health]. Currently, it's [combat_health]." if(special_attack_cooldown == 0) . += "Its special move light is green and is ready!" else @@ -685,28 +700,13 @@ * * Arguments * - * src: the defending toy, the toy that is hit by another toy with to start the battle (not an 'arg' but relevant / makes it easier to read if you understand) + * src: the defending toy, the toy that is hit by another toy to start the battle (not an 'arg' but relevant / makes it easier to read if you understand) * attacker: the attacking toy, the toy you use on another toy to start the battle. in PvP, this is the opponent's toy * referee: the user, the one who is holding the toys / controlling the fight * opponent: optional arg used in Mech PvP battles: the other person who is taking part in the fight */ -/obj/item/toy/prize/proc/mechaBrawl(obj/item/toy/prize/attacker, mob/living/carbon/referee, mob/living/opponent) - if(src.in_combat || attacker.in_combat) - return - - if(timer < world.time) - timer = world.time + cooldown*8 - else - to_chat(referee, "[name] isn't ready for battle.") - return - - if(attacker.timer < world.time) - attacker.timer = world.time + cooldown*8 - else - to_chat(referee, "[attacker.name] isn't ready for battle.") - opponent?.to_chat(opponent, "[attacker.name] isn't ready for battle.") - return - +/obj/item/toy/prize/proc/mechaBrawl(obj/item/toy/prize/attacker, mob/living/carbon/referee, mob/living/carbon/opponent) + //A GOOD DAY FOR A SWELL BATTLE! referee.visible_message(" [referee.name] collides [attacker.name] with [name]! Looks like they're preparing for a brawl! ", \ " You collide [attacker.name] into [name], sparking a fierce battle! ", \ " You hear hard plastic smacking into hard plastic.", COMBAT_MESSAGE_RANGE) @@ -717,45 +717,62 @@ src.in_combat = TRUE attacker.in_combat = TRUE + timer = world.time + cooldown*8 + attacker.timer = world.time + attacker.cooldown*8 + sleep(15) //--THE BATTLE BEGINS-- while(combat_health > 0 && attacker.combat_health > 0) + //the toys need to be next to each other to fight! + //if it's player vs themself: They need to be able to "control" both mechs (either adjacent or using TK) + //if it's player vs player: Both players need to be able to "control" their mechs (either adjacent or using TK) + //if the two toys aren't next to each other, the battle ends - if(!in_range(src, attacker)) + if(!in_range(src, attacker)) referee.visible_message(" [attacker.name] and [name] separate, ending the battle. ", \ " [attacker.name] and [name] separate, ending the battle. ") break - //if the referee isn't next either both toys (and doesn't have telekinesis), the battle ends - if((!in_range(src, referee) || !in_range(attacker, referee)) && !(referee.dna.check_mutation(TK))) + //if the referee isn't next to the defending toy (and doesn't have telekinesis), the battle ends + if((!in_range(src, referee) && !(referee.dna.check_mutation(TK)))) referee.visible_message(" [referee.name] seperates from [name] and [attacker.name], ending the battle.", \ " You separate [attacker.name] and [name], ending the battle. ") break //if it's PVP and the opponent is not next to the attacking toy (and doesn't have telekinesis), the battle ends - if(opponent && !in_range(attacker, opponent) && !(referee.dna.check_mutation(TK))) - opponent.visible_message(" [referee.name] seperates from [attacker.name], ending the battle.", \ - " You separate from [attacker.name], ending the battle. ") - break + if(opponent) + if(!in_range(attacker, opponent) && !(opponent.dna.check_mutation(TK))) + opponent.visible_message(" [referee.name] seperates from [attacker.name], ending the battle.", \ + " You separate from [attacker.name], ending the battle. ") + break + //if it's not PVP and the referee isn't next to the attacking toy (and doesn't have telekinesis), the battle ends + else + if (!in_range(attacker, referee) && !(referee.dna.check_mutation(TK))) + referee.visible_message(" [referee.name] seperates from [name] and [attacker.name], ending the battle.", \ + " You separate [attacker.name] and [name], ending the battle. ") + break + //before we do anything - deal with charged attacks if(special_attack_charged) specialAttackMove(attacker) else if(attacker.special_attack_charged) attacker.specialAttackMove(src) else + //process the cooldowns if(special_attack_cooldown > 0) special_attack_cooldown-- if(attacker.special_attack_cooldown > 0) attacker.special_attack_cooldown-- - switch(rand(1,6)) - if(1 to 2) //attacker wins - if(attacker.combat_health <= round(attacker.max_combat_health/3) && special_attack_cooldown == 0) //if health is less than 1/3 and special off CD, use it + //combat commences + switch(rand(1,8)) + if(1 to 3) //attacker wins + if(attacker.special_attack_cooldown == 0 && attacker.combat_health <= round(attacker.max_combat_health/3)) //if health is less than 1/3 and special off CD, use it attacker.special_attack_charged = TRUE attacker_controller.visible_message(" [attacker.name] begins charging his special attack!! ", \ " You begin charging [attacker.name]'s special attack! ") else //just attack attacker.SpinAnimation(5, 0) - playsound(attacker, 'sound/mecha/mechstep.ogg', 20, TRUE) + playsound(attacker, 'sound/mecha/mechstep.ogg', 30, TRUE) combat_health-- attacker_controller.visible_message(" [attacker.name] devastates [name]! ", \ " You ram [attacker.name] into [name]! ", \ @@ -766,7 +783,7 @@ attacker_controller.visible_message(" ...and lands a CRIPPLING BLOW! ", \ " ...and you land a CRIPPLING blow on [name]! ", null, COMBAT_MESSAGE_RANGE) - if(3) //both lose + if(4) //both lose attacker.SpinAnimation(5, 0) SpinAnimation(5, 0) combat_health-- @@ -781,7 +798,7 @@ attacker_controller.visible_message(" [name] and [attacker.name] clash dramatically, causing sparks to fly! ", \ " [name] and [attacker.name] clash dramatically, causing sparks to fly! ", \ " You hear hard plastic rubbing against hard plastic.", COMBAT_MESSAGE_RANGE) - if(4) //both win + if(5) //both win playsound(attacker, 'sound/weapons/parry.ogg', 20, TRUE) if(prob(50)) referee.visible_message(" [name]'s attack deflects off of [attacker.name]. ", \ @@ -792,14 +809,14 @@ " [attacker.name]'s attack deflects off of [name]. ", \ " You hear hard plastic bouncing off hard plastic.", COMBAT_MESSAGE_RANGE) - if(5 to 6) //defender wins - if(combat_health <= round(max_combat_health/3) && special_attack_cooldown == 0) //if health is less than 1/3 and special off CD, use it + if(6 to 8) //defender wins + if(special_attack_cooldown == 0 && combat_health <= round(max_combat_health/3)) //if health is less than 1/3 and special off CD, use it special_attack_charged = TRUE referee.visible_message(" [name] begins charging his special attack!! ", \ " You begin charging [name]'s special attack! ") else //just attack SpinAnimation(5, 0) - playsound(src, 'sound/mecha/mechstep.ogg', 20, TRUE) + playsound(src, 'sound/mecha/mechstep.ogg', 30, TRUE) attacker.combat_health-- referee.visible_message(" [name] smashes [attacker.name]! ", \ " You smash [name] into [attacker.name]! ", \ @@ -822,25 +839,27 @@ wins++ attacker.losses++ playsound(attacker, 'sound/effects/light_flicker.ogg', 20, TRUE) - attacker_controller.visible_message(" [attacker.name] falls apart!", \ - " [attacker.name] falls apart!", null, COMBAT_MESSAGE_RANGE) + referee.visible_message(" [attacker.name] falls apart!", \ + " [attacker.name] falls apart!", null, COMBAT_MESSAGE_RANGE) if(!quiet) say("YOU SHOULDN'T HAVE ATTACKED ME") - referee.visible_message(" [name] destroys [attacker.name] and walks away victorious!", \ - " You raise up [name] victoriously over [attacker.name]!") + attacker_controller.visible_message(" [name] destroys [attacker.name] and walks away victorious!", \ + " You raise up [name] victoriously over [attacker.name]!") else if (combat_health <= 0) //attacker wins attacker.wins++ losses++ playsound(src, 'sound/effects/light_flicker.ogg', 20, TRUE) - referee.visible_message(" [name] collapses!", \ - " [name] collapses!", null, COMBAT_MESSAGE_RANGE) + attacker_controller.visible_message(" [name] collapses!", \ + " [name] collapses!", null, COMBAT_MESSAGE_RANGE) if(!attacker.quiet) attacker.say("EASY FIGHT") - attacker_controller.visible_message(" [attacker.name] demolishes [name] and walks away victorious!", \ + referee.visible_message(" [attacker.name] demolishes [name] and walks away victorious!", \ " You raise up [attacker.name] proudly over [name]!") else //both win? - (!quiet)? say("I WENT EASY ON YOU"):null - (!attacker.quiet)? attacker.say("OF COURSE YOU DID"):null + if(!quiet) + say("I WENT EASY ON YOU") + if(!attacker.quiet) + attacker.say("OF COURSE YOU DID") src.in_combat = FALSE attacker.in_combat = FALSE @@ -850,6 +869,40 @@ return +/** + * This proc checks if a battle can be initiated between src and attacker. + * + * Both SRC's and attacker's timer are checked if they're on cooldown, and + * both SRC and attacker are checked if they are in combat already. + * If any of the above are true, the proc returns FALSE and sends a message to user (and target, if included) otherwise, it returns TRUE + * + * Arguments + * + * src: the defending toy, user's toy (not an 'arg' but relevant / makes it easier to read if you understand) + * attacker: the attacking toy, the target's toy if target exists + * user: the user who is initiating the battle + * target: optional arg used in Mech PvP battles: the other person who is taking part in the fight + */ +/obj/item/toy/prize/proc/checkBattleStart(obj/item/toy/prize/attacker, mob/living/carbon/user, mob/living/carbon/target) + if(attacker.in_combat) + to_chat(user, "[target?target.p_their() : "Your" ] [attacker.name] is in combat.") + target?.to_chat(target, "Your [attacker.name] is in combat.") + return FALSE + if(in_combat) + to_chat(user, "Your [name] is in combat.") + target?.to_chat(target, "[target.p_their()] [name] is in combat.") + return FALSE + if(attacker.timer > world.time) + to_chat(user, "[target?target.p_their() : "Your" ] [attacker.name] isn't ready for battle.") + target?.to_chat(target, "Your [attacker.name] isn't ready for battle.") + return FALSE + if(timer > world.time) + to_chat(user, "Your [name] isn't ready for battle.") + target?.to_chat(target, "[target.p_their()] [name] isn't ready for battle.") + return FALSE + + return TRUE + /** * Processes any special attack moves that happen in the battle (called in the mechaBattle proc). * From eda752cbd03c4d13aa15773191615de981fd9e8d Mon Sep 17 00:00:00 2001 From: MrMelbert Date: Sun, 21 Jun 2020 16:49:27 -0500 Subject: [PATCH 005/196] polish --- code/game/objects/items/toys.dm | 311 +++++++++++++++++--------------- 1 file changed, 167 insertions(+), 144 deletions(-) diff --git a/code/game/objects/items/toys.dm b/code/game/objects/items/toys.dm index 9cab426bbbc..537bfe2cb80 100644 --- a/code/game/objects/items/toys.dm +++ b/code/game/objects/items/toys.dm @@ -32,6 +32,8 @@ #define SPECIAL_ATTACK_UTILITY 3 #define SPECIAL_ATTACK_OTHER 4 +/// Max length of a mech battle +#define MAX_BATTLE_LENGTH 50 /obj/item/toy throwforce = 0 @@ -535,6 +537,7 @@ verb_ask = "beeps" verb_exclaim = "beeps" verb_yell = "beeps" + w_class = WEIGHT_CLASS_SMALL /// Timer when it'll be off cooldown var/timer = 0 /// Cooldown between play sessions - 8x longer after a battle @@ -563,11 +566,11 @@ var/wins = 0 /// ...And their loss count in combat var/losses = 0 - w_class = WEIGHT_CLASS_SMALL /obj/item/toy/prize/Initialize() . = ..() - max_combat_health = combat_health + desc = "Mini-Mecha action figure! Collect them all! Attack your friends or another mech with one to initiate epic mech combat! [desc]." + combat_health = max_combat_health switch(special_attack_type) if(SPECIAL_ATTACK_DAMAGE) special_attack_type_message = "an aggressive move, which deals bonus damage." @@ -590,35 +593,43 @@ else . = ..() -/obj/item/toy/prize/attackby(obj/item/I, mob/living/user) +/obj/item/toy/prize/attackby(obj/item/I, mob/living/user) //I is user's toy if(istype(I, /obj/item/toy/prize)) //if you attack a mech with a mech, initiate combat between them var/obj/item/toy/prize/P = I - if(checkBattleStart(P, user)) + if(checkBattleStart(user, P)) mechaBrawl(P, user) ..() -/obj/item/toy/prize/attack(mob/living/carbon/human/target, mob/living/carbon/human/user) - for(var/obj/item/toy/prize/I in target.held_items) - if(istype(I, /obj/item/toy/prize)) //if you attack someone with a mech who's also holding a mech, offer to battle them - var/obj/item/toy/prize/P = I +/obj/item/toy/prize/attack(mob/living/carbon/human/target, mob/living/carbon/human/user) //src is user's toy + if(target == user) + to_chat(user, "Target another toy mech if you want to start a battle with yourself.") + return + else if(user.a_intent != INTENT_HARM) + if(wants_to_battle) //prevent spamming someone with offers + to_chat(user, "You already are offering battle to someone!") + return + if(!checkBattleStart(user)) //if the user's mech isn't ready, don't bother checking + return - if(!checkBattleStart(P, user, target)) - break + for(var/obj/item/I in target.held_items) + if(istype(I, /obj/item/toy/prize)) //if you attack someone with a mech who's also holding a mech, offer to battle them + var/obj/item/toy/prize/P = I + if(!P.checkBattleStart(target, null, user)) //check if the attacker mech is ready + break + + //slap them with the metaphorical white glove + if(P.wants_to_battle) //if the target mech wants to battle, initiate the battle from their POV + mechaBrawl(P, target, user) //P = challenger's mech / SRC = defender's mech / user = defender / target = challenger + P.wants_to_battle = FALSE + return + + //extend the offer of battle to the other mech + to_chat(user, "You offer battle to [target.name]!") + to_chat(target, "[user.name] wants to battle with [user.p_their()] [name]! Attack them with a toy mech to initiate combat.") + wants_to_battle = TRUE + addtimer(CALLBACK(src, .proc/withdrawOffer, user), 60) + return - //slap them with the metaphorical white glove - if(wants_to_battle) //prevent spamming someone with offers - to_chat(user, "You already are offering battle!") - break - if(P.wants_to_battle) //if the target mech wants to battle, initiate the battle from their POV - P.mechaBrawl(src, user, target) //P = challenger's mech / SRC = defender's mech / user = defender / target = challenger - P.wants_to_battle = FALSE - return - else //extend the offer of battle to the other mech - to_chat(user, "You offer battle to [target.name] and [target.p_their()] [P.name]!") - to_chat(target, "[user.name] wants to battle with [target.p_their()] [name]! Attack them with your [P.name] to initiate combat.") - wants_to_battle = TRUE - addtimer(CALLBACK(src, .proc/withdrawOffer, user), 60) - ..() /** @@ -629,15 +640,16 @@ * user: the user wanting to do battle */ /obj/item/toy/prize/proc/withdrawOffer(mob/living/carbon/user) - wants_to_battle = FALSE - to_chat(user, "You get the feeling they don't want to battle.") + if(wants_to_battle) + wants_to_battle = FALSE + to_chat(user, "You get the feeling they don't want to battle.") /obj/item/toy/prize/suicide_act(mob/living/carbon/user) if(in_combat) - to_chat(user, "[name] is in battle, let it finish first.") + to_chat(user, "[src] is in battle, let it finish first.") return - user.visible_message("[user] begins a fight with [name]! There's no way they can win, it looks like [user.p_theyre()] trying to commit suicide!") + user.visible_message("[user] begins a fight [user.p_they()] can't win with [src]! It looks like [user.p_theyre()] trying to commit suicide!") in_combat = TRUE sleep(20) @@ -662,15 +674,16 @@ else SpinAnimation(5, 0) sleep(15) - if(!in_range(src, user)) + if(!in_range(src, user)) //run away? coward say("PATHETIC") return SHAME user.adjustBruteLoss(450) + in_combat = FALSE - say("AN EASY WIN. MY POWER INCREASES WITH THIS OFFERING.") + say("AN EASY WIN. MY POWER INCREASES.") // steal a soul, become swole add_atom_colour(rgb(255, 115, 115), ADMIN_COLOUR_PRIORITY) - max_combat_health = round(max_combat_health*1.5) + max_combat_health = round(max_combat_health*1.5 + 0.1) combat_health = max_combat_health wins++ return BRUTELOSS @@ -680,12 +693,9 @@ . += "This toy's special attack is [special_attack_cry], [special_attack_type_message] " if(in_combat) . += "This toy has a maximum health of [max_combat_health]. Currently, it's [combat_health]." - if(special_attack_cooldown == 0) - . += "Its special move light is green and is ready!" - else - . += "Its special move light is flashing red." + . += "Its special move light is [special_attack_cooldown? "flashing red." : "green and is ready!"]" else - . += "This toy's got a maximum health of [max_combat_health]." + . += "This toy has a maximum health of [max_combat_health]." if(wins || losses) . += "This toy has [wins] wins, and [losses] losses." @@ -700,21 +710,23 @@ * * Arguments * - * src: the defending toy, the toy that is hit by another toy to start the battle (not an 'arg' but relevant / makes it easier to read if you understand) - * attacker: the attacking toy, the toy you use on another toy to start the battle. in PvP, this is the opponent's toy - * referee: the user, the one who is holding the toys / controlling the fight - * opponent: optional arg used in Mech PvP battles: the other person who is taking part in the fight + * src: the defending toy, the toy that is hit by the attacker to start the battle + * attacker: the attacking toy, the toy in the attacker_controller's hands + * attacker_controller: the user, the one who is holding the toys / controlling the fight + * opponent: optional arg used in Mech PvP battles: the other person who is taking part in the fight (controls src) */ -/obj/item/toy/prize/proc/mechaBrawl(obj/item/toy/prize/attacker, mob/living/carbon/referee, mob/living/carbon/opponent) +/obj/item/toy/prize/proc/mechaBrawl(obj/item/toy/prize/attacker, mob/living/carbon/attacker_controller, mob/living/carbon/opponent) //A GOOD DAY FOR A SWELL BATTLE! - referee.visible_message(" [referee.name] collides [attacker.name] with [name]! Looks like they're preparing for a brawl! ", \ - " You collide [attacker.name] into [name], sparking a fierce battle! ", \ - " You hear hard plastic smacking into hard plastic.", COMBAT_MESSAGE_RANGE) + attacker_controller.visible_message(" [attacker_controller.name] collides [attacker] with [src]! Looks like they're preparing for a brawl! ", \ + " You collide [attacker] into [src], sparking a fierce battle! ", \ + " You hear hard plastic smacking into hard plastic.", COMBAT_MESSAGE_RANGE) - /// Who's in control of the attacker? - var/mob/living/attacker_controller = (opponent)? opponent : referee - - src.in_combat = TRUE + /// Who's in control of the defender (src)? + var/mob/living/carbon/src_controller = (opponent)? opponent : attacker_controller + /// How long has the battle been going? + var/battle_length = 0 + + in_combat = TRUE attacker.in_combat = TRUE timer = world.time + cooldown*8 @@ -722,7 +734,7 @@ sleep(15) //--THE BATTLE BEGINS-- - while(combat_health > 0 && attacker.combat_health > 0) + while(combat_health > 0 && attacker.combat_health > 0 && battle_length < MAX_BATTLE_LENGTH) //the toys need to be next to each other to fight! //if it's player vs themself: They need to be able to "control" both mechs (either adjacent or using TK) @@ -730,31 +742,36 @@ //if the two toys aren't next to each other, the battle ends if(!in_range(src, attacker)) - referee.visible_message(" [attacker.name] and [name] separate, ending the battle. ", \ - " [attacker.name] and [name] separate, ending the battle. ") + attacker_controller.visible_message(" [attacker] and [src] separate, ending the battle. ", \ + " [attacker] and [src] separate, ending the battle. ") break - //if the referee isn't next to the defending toy (and doesn't have telekinesis), the battle ends - if((!in_range(src, referee) && !(referee.dna.check_mutation(TK)))) - referee.visible_message(" [referee.name] seperates from [name] and [attacker.name], ending the battle.", \ - " You separate [attacker.name] and [name], ending the battle. ") + //if the attacker_controller isn't next to the attacking toy (and doesn't have telekinesis), the battle ends + if((!in_range(attacker, attacker_controller) && !(attacker_controller.dna.check_mutation(TK)))) + attacker_controller.visible_message(" [attacker_controller.name] seperates from [attacker], ending the battle.", \ + " You separate from [attacker], ending the battle. ") break - //if it's PVP and the opponent is not next to the attacking toy (and doesn't have telekinesis), the battle ends + //if it's PVP and the opponent is not next to the defending(src) toy (and doesn't have telekinesis), the battle ends if(opponent) - if(!in_range(attacker, opponent) && !(opponent.dna.check_mutation(TK))) - opponent.visible_message(" [referee.name] seperates from [attacker.name], ending the battle.", \ - " You separate from [attacker.name], ending the battle. ") + if(!in_range(src, opponent) && !(opponent.dna.check_mutation(TK))) + opponent.visible_message(" [opponent.name] seperates from [src], ending the battle.", \ + " You separate from [src], ending the battle. ") break - //if it's not PVP and the referee isn't next to the attacking toy (and doesn't have telekinesis), the battle ends + //if it's not PVP and the attacker_controller isn't next to the defending toy (and doesn't have telekinesis), the battle ends else - if (!in_range(attacker, referee) && !(referee.dna.check_mutation(TK))) - referee.visible_message(" [referee.name] seperates from [name] and [attacker.name], ending the battle.", \ - " You separate [attacker.name] and [name], ending the battle. ") + if (!in_range(src, attacker_controller) && !(attacker_controller.dna.check_mutation(TK))) + attacker_controller.visible_message(" [attacker_controller.name] seperates from [src] and [attacker], ending the battle.", \ + " You separate [attacker] and [src], ending the battle. ") break //before we do anything - deal with charged attacks if(special_attack_charged) + src_controller.visible_message(" [src] unleashes its special attack!! ", \ + " You unleash [src]'s special attack! ") specialAttackMove(attacker) else if(attacker.special_attack_charged) + + attacker_controller.visible_message(" [attacker] unleashes its special attack!! ", \ + " You unleash [attacker]'s special attack! ") attacker.specialAttackMove(src) else //process the cooldowns @@ -768,100 +785,105 @@ if(1 to 3) //attacker wins if(attacker.special_attack_cooldown == 0 && attacker.combat_health <= round(attacker.max_combat_health/3)) //if health is less than 1/3 and special off CD, use it attacker.special_attack_charged = TRUE - attacker_controller.visible_message(" [attacker.name] begins charging his special attack!! ", \ - " You begin charging [attacker.name]'s special attack! ") + attacker_controller.visible_message(" [attacker] begins charging its special attack!! ", \ + " You begin charging [attacker]'s special attack! ") else //just attack attacker.SpinAnimation(5, 0) playsound(attacker, 'sound/mecha/mechstep.ogg', 30, TRUE) combat_health-- - attacker_controller.visible_message(" [attacker.name] devastates [name]! ", \ - " You ram [attacker.name] into [name]! ", \ + attacker_controller.visible_message(" [attacker] devastates [src]! ", \ + " You ram [attacker] into [src]! ", \ " You hear hard plastic smacking hard plastic.", COMBAT_MESSAGE_RANGE) if(prob(5)) combat_health-- playsound(src, 'sound/effects/meteorimpact.ogg', 20, TRUE) attacker_controller.visible_message(" ...and lands a CRIPPLING BLOW! ", \ - " ...and you land a CRIPPLING blow on [name]! ", null, COMBAT_MESSAGE_RANGE) + " ...and you land a CRIPPLING blow on [src]! ", null, COMBAT_MESSAGE_RANGE) if(4) //both lose attacker.SpinAnimation(5, 0) SpinAnimation(5, 0) combat_health-- attacker.combat_health-- - do_sparks(3, FALSE, src) - do_sparks(3, FALSE, attacker) + do_sparks(2, FALSE, src) + do_sparks(2, FALSE, attacker) if(prob(50)) - referee.visible_message(" [attacker.name] and [name] clash dramatically, causing sparks to fly! ", \ - " [attacker.name] and [name] clash dramatically, causing sparks to fly! ", \ - " You hear hard plastic rubbing against hard plastic.", COMBAT_MESSAGE_RANGE) - else - attacker_controller.visible_message(" [name] and [attacker.name] clash dramatically, causing sparks to fly! ", \ - " [name] and [attacker.name] clash dramatically, causing sparks to fly! ", \ + attacker_controller.visible_message(" [attacker] and [src] clash dramatically, causing sparks to fly! ", \ + " [attacker] and [src] clash dramatically, causing sparks to fly! ", \ " You hear hard plastic rubbing against hard plastic.", COMBAT_MESSAGE_RANGE) + else + src_controller.visible_message(" [src] and [attacker] clash dramatically, causing sparks to fly! ", \ + " [src] and [attacker] clash dramatically, causing sparks to fly! ", \ + " You hear hard plastic rubbing against hard plastic.", COMBAT_MESSAGE_RANGE) if(5) //both win playsound(attacker, 'sound/weapons/parry.ogg', 20, TRUE) if(prob(50)) - referee.visible_message(" [name]'s attack deflects off of [attacker.name]. ", \ - " [name]'s attack deflects off of [attacker.name]. ", \ - " You hear hard plastic bouncing off hard plastic.", COMBAT_MESSAGE_RANGE) - else - attacker_controller.visible_message(" [attacker.name]'s attack deflects off of [name]. ", \ - " [attacker.name]'s attack deflects off of [name]. ", \ + attacker_controller.visible_message(" [src]'s attack deflects off of [attacker]. ", \ + " [src]'s attack deflects off of [attacker]. ", \ " You hear hard plastic bouncing off hard plastic.", COMBAT_MESSAGE_RANGE) + else + src_controller.visible_message(" [attacker]'s attack deflects off of [src]. ", \ + " [attacker]'s attack deflects off of [src]. ", \ + " You hear hard plastic bouncing off hard plastic.", COMBAT_MESSAGE_RANGE) if(6 to 8) //defender wins if(special_attack_cooldown == 0 && combat_health <= round(max_combat_health/3)) //if health is less than 1/3 and special off CD, use it special_attack_charged = TRUE - referee.visible_message(" [name] begins charging his special attack!! ", \ - " You begin charging [name]'s special attack! ") + src_controller.visible_message(" [src] begins charging its special attack!! ", \ + " You begin charging [src]'s special attack! ") else //just attack SpinAnimation(5, 0) playsound(src, 'sound/mecha/mechstep.ogg', 30, TRUE) attacker.combat_health-- - referee.visible_message(" [name] smashes [attacker.name]! ", \ - " You smash [name] into [attacker.name]! ", \ - " You hear hard plastic smashing hard plastic.", COMBAT_MESSAGE_RANGE) + src_controller.visible_message(" [src] smashes [attacker]! ", \ + " You smash [src] into [attacker]! ", \ + " You hear hard plastic smashing hard plastic.", COMBAT_MESSAGE_RANGE) if(prob(5)) attacker.combat_health-- playsound(attacker, 'sound/effects/meteorimpact.ogg', 20, TRUE) - referee.visible_message(" ...and lands a CRIPPLING BLOW! ", \ - " ...and you land a CRIPPLING blow on [attacker.name]! ", null, COMBAT_MESSAGE_RANGE) + src_controller.visible_message(" ...and lands a CRIPPLING BLOW! ", \ + " ...and you land a CRIPPLING blow on [attacker]! ", null, COMBAT_MESSAGE_RANGE) else - referee.visible_message(" [name] and [attacker.name] stand around awkwardly.", \ - " You don't know what to do next.") + attacker_controller.visible_message(" [src] and [attacker] stand around awkwardly.", \ + " You don't know what to do next.") + + battle_length++ sleep(10) + /// Lines chosen for the winning mech + var/list/winlines = list("YOU'RE NOTHING BUT SCRAP!", "I'LL YIELD TO NONE!", "GLORY IS MINE!", "AN EASY FIGHT.", "YOU SHOULD HAVE NEVER FACED ME.") + if(attacker.combat_health <= 0 && combat_health <= 0) //both lose playsound(src, 'sound/machines/warning-buzzer.ogg', 20, TRUE) - referee.visible_message(" MUTUALLY ASSURED DESTRUCTION!! [name] and [attacker.name] both end up destroyed!", \ - " Both [name] and [attacker.name] are destroyed!") + attacker_controller.visible_message(" MUTUALLY ASSURED DESTRUCTION!! [src] and [attacker] both end up destroyed!", \ + " Both [src] and [attacker] are destroyed!") else if(attacker.combat_health <= 0) //src wins wins++ attacker.losses++ playsound(attacker, 'sound/effects/light_flicker.ogg', 20, TRUE) - referee.visible_message(" [attacker.name] falls apart!", \ - " [attacker.name] falls apart!", null, COMBAT_MESSAGE_RANGE) + attacker_controller.visible_message(" [attacker] falls apart!", \ + " [attacker] falls apart!", null, COMBAT_MESSAGE_RANGE) if(!quiet) - say("YOU SHOULDN'T HAVE ATTACKED ME") - attacker_controller.visible_message(" [name] destroys [attacker.name] and walks away victorious!", \ - " You raise up [name] victoriously over [attacker.name]!") + say("[pick(winlines)]") + src_controller.visible_message(" [src] destroys [attacker] and walks away victorious!", \ + " You raise up [src] victoriously over [attacker]!") else if (combat_health <= 0) //attacker wins attacker.wins++ losses++ playsound(src, 'sound/effects/light_flicker.ogg', 20, TRUE) - attacker_controller.visible_message(" [name] collapses!", \ - " [name] collapses!", null, COMBAT_MESSAGE_RANGE) + src_controller.visible_message(" [src] collapses!", \ + " [src] collapses!", null, COMBAT_MESSAGE_RANGE) if(!attacker.quiet) - attacker.say("EASY FIGHT") - referee.visible_message(" [attacker.name] demolishes [name] and walks away victorious!", \ - " You raise up [attacker.name] proudly over [name]!") + attacker.say("[pick(winlines)]") + attacker_controller.visible_message(" [attacker] demolishes [src] and walks away victorious!", \ + " You raise up [attacker] proudly over [src]!") else //both win? if(!quiet) - say("I WENT EASY ON YOU") + say("I WENT EASY ON YOU.") if(!attacker.quiet) - attacker.say("OF COURSE YOU DID") + attacker.say("OF COURSE YOU DID.") - src.in_combat = FALSE + in_combat = FALSE attacker.in_combat = FALSE combat_health = max_combat_health @@ -872,19 +894,18 @@ /** * This proc checks if a battle can be initiated between src and attacker. * - * Both SRC's and attacker's timer are checked if they're on cooldown, and - * both SRC and attacker are checked if they are in combat already. + * Both SRC and attacker (if attacker is included) timers are checked if they're on cooldown, and + * both SRC and attacker (if attacker is included) are checked if they are in combat already. * If any of the above are true, the proc returns FALSE and sends a message to user (and target, if included) otherwise, it returns TRUE * * Arguments * - * src: the defending toy, user's toy (not an 'arg' but relevant / makes it easier to read if you understand) - * attacker: the attacking toy, the target's toy if target exists * user: the user who is initiating the battle - * target: optional arg used in Mech PvP battles: the other person who is taking part in the fight + * attacker: optional arg for checking two mechs at once + * target: optional arg used in Mech PvP battles (if used, attacker is target's toy) */ -/obj/item/toy/prize/proc/checkBattleStart(obj/item/toy/prize/attacker, mob/living/carbon/user, mob/living/carbon/target) - if(attacker.in_combat) +/obj/item/toy/prize/proc/checkBattleStart(mob/living/carbon/user, obj/item/toy/prize/attacker, mob/living/carbon/target) + if(attacker && attacker.in_combat) to_chat(user, "[target?target.p_their() : "Your" ] [attacker.name] is in combat.") target?.to_chat(target, "Your [attacker.name] is in combat.") return FALSE @@ -892,7 +913,7 @@ to_chat(user, "Your [name] is in combat.") target?.to_chat(target, "[target.p_their()] [name] is in combat.") return FALSE - if(attacker.timer > world.time) + if(attacker && attacker.timer > world.time) to_chat(user, "[target?target.p_their() : "Your" ] [attacker.name] isn't ready for battle.") target?.to_chat(target, "Your [attacker.name] isn't ready for battle.") return FALSE @@ -929,7 +950,7 @@ if(SPECIAL_ATTACK_UTILITY) //+1 heal, +1 damage victim.combat_health-- combat_health++ - playsound(src, 'sound/mecha/mechmove01.ogg', 20, TRUE) + playsound(src, 'sound/mecha/mechmove01.ogg', 30, TRUE) if(SPECIAL_ATTACK_OTHER) //other superSpecialAttack(victim) else @@ -944,28 +965,28 @@ * victim: the toy being hit by the super special move (doesn't necessarily need to be used) */ /obj/item/toy/prize/proc/superSpecialAttack(obj/item/toy/prize/victim) - visible_message(" [name] does a cool flip.") + visible_message(" [src] does a cool flip.") /obj/item/toy/prize/ripley name = "toy Ripley" - desc = "Mini-Mecha action figure! Collect them all! 1/13." - combat_health = 3 + desc = "1/13" + max_combat_health = 3 special_attack_type = SPECIAL_ATTACK_DAMAGE special_attack_cry = "OMEGA DRILL" /obj/item/toy/prize/fireripley name = "toy Firefighting Ripley" - desc = "Mini-Mecha action figure! Collect them all! 2/13." + desc = "2/13" icon_state = "fireripleytoy" - combat_health = 4 + max_combat_health = 4 special_attack_type = SPECIAL_ATTACK_UTILITY special_attack_cry = "FIRE SHIELD" /obj/item/toy/prize/deathripley name = "toy Deathsquad Ripley" - desc = "Mini-Mecha action figure! Collect them all! 3/13." + desc = "3/13" icon_state = "deathripleytoy" - combat_health = 5 + max_combat_health = 5 special_attack_type = SPECIAL_ATTACK_OTHER special_attack_type_message = "instantly destroys the opposing mech if its health is less than this mech's health." special_attack_cry = "KILLER CLAMP" @@ -980,25 +1001,25 @@ /obj/item/toy/prize/gygax name = "toy Gygax" - desc = "Mini-Mecha action figure! Collect them all! 4/13." + desc = "4/13" icon_state = "gygaxtoy" - combat_health = 5 + max_combat_health = 5 special_attack_type = SPECIAL_ATTACK_UTILITY special_attack_cry = "SUPER SERVOS" /obj/item/toy/prize/durand name = "toy Durand" - desc = "Mini-Mecha action figure! Collect them all! 5/13." + desc = "5/13" icon_state = "durandtoy" - combat_health = 6 + max_combat_health = 6 special_attack_type = SPECIAL_ATTACK_HEAL special_attack_cry = "SHIELD OF PROTECTION" /obj/item/toy/prize/honk name = "toy H.O.N.K." - desc = "Mini-Mecha action figure! Collect them all! 6/13." + desc = "6/13" icon_state = "honktoy" - combat_health = 4 + max_combat_health = 4 special_attack_type = SPECIAL_ATTACK_OTHER special_attack_type_message = "puts the opposing mech's special move on cooldown and heals this mech." special_attack_cry = "MEGA HORN" @@ -1010,63 +1031,64 @@ /obj/item/toy/prize/marauder name = "toy Marauder" - desc = "Mini-Mecha action figure! Collect them all! 7/13." + desc = "7/13" icon_state = "maraudertoy" - combat_health = 7 + max_combat_health = 7 special_attack_type = SPECIAL_ATTACK_DAMAGE special_attack_cry = "BEAM BLAST" /obj/item/toy/prize/seraph name = "toy Seraph" - desc = "Mini-Mecha action figure! Collect them all! 8/13." + desc = "8/13" icon_state = "seraphtoy" - combat_health = 8 + max_combat_health = 8 special_attack_type = SPECIAL_ATTACK_DAMAGE special_attack_cry = "ROCKET BARRAGE" /obj/item/toy/prize/mauler name = "toy Mauler" - desc = "Mini-Mecha action figure! Collect them all! 9/13." + desc = "9/13" icon_state = "maulertoy" - combat_health = 8 + max_combat_health = 8 special_attack_type = SPECIAL_ATTACK_DAMAGE special_attack_cry = "BULLET STORM" /obj/item/toy/prize/odysseus name = "toy Odysseus" - desc = "Mini-Mecha action figure! Collect them all! 10/13." + desc = "10/13" icon_state = "odysseustoy" - combat_health = 4 + max_combat_health = 4 special_attack_type = SPECIAL_ATTACK_HEAL special_attack_cry = "MECHA BEAM" /obj/item/toy/prize/phazon name = "toy Phazon" - desc = "Mini-Mecha action figure! Collect them all! 11/13." + desc = "11/13" icon_state = "phazontoy" - combat_health = 6 + max_combat_health = 6 special_attack_type = SPECIAL_ATTACK_UTILITY special_attack_cry = "NO-CLIP" /obj/item/toy/prize/reticence name = "toy Reticence" - desc = "Mini-Mecha action figure! Collect them all! 12/13." + desc = "12/13" icon_state = "reticencetoy" quiet = TRUE - combat_health = 4 + max_combat_health = 4 special_attack_type = SPECIAL_ATTACK_OTHER special_attack_type_message = "has a lower cooldown than normal special moves and deals damage." special_attack_cry = "*wave" /obj/item/toy/prize/reticence/superSpecialAttack(obj/item/toy/prize/victim) - special_attack_cooldown = 1 //Has a very low cooldown and does minor damage. - victim.combat_health-- + special_attack_cooldown-- //Has a lower cooldown... + victim.special_attack_cooldown++ //and increases the opponent's cooldown by 1... + victim.combat_health-- //and some free damage. /obj/item/toy/prize/clarke name = "toy Clarke" - desc = "Mini-Mecha action figure! Collect them all! 13/13." + desc = "13/13" icon_state = "clarketoy" - combat_health = 4 + max_combat_health = 4 special_attack_type = SPECIAL_ATTACK_UTILITY special_attack_cry = "ROLL OUT" @@ -2032,3 +2054,4 @@ #undef SPECIAL_ATTACK_DAMAGE #undef SPECIAL_ATTACK_UTILITY #undef SPECIAL_ATTACK_OTHER +#undef MAX_BATTLE_LENGTH From ce9df7556a4974b338b9afc26b655d5e6f3ab5fa Mon Sep 17 00:00:00 2001 From: MrMelbert Date: Tue, 23 Jun 2020 20:27:05 -0500 Subject: [PATCH 006/196] restores a deleted attack_hand --- code/game/objects/items/toys.dm | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/code/game/objects/items/toys.dm b/code/game/objects/items/toys.dm index 537bfe2cb80..fff5c8a50d1 100644 --- a/code/game/objects/items/toys.dm +++ b/code/game/objects/items/toys.dm @@ -593,6 +593,13 @@ else . = ..() +/obj/item/toy/prize/attack_hand(mob/user) + . = ..() + if(.) + return + if(loc == user) + attack_self(user) + /obj/item/toy/prize/attackby(obj/item/I, mob/living/user) //I is user's toy if(istype(I, /obj/item/toy/prize)) //if you attack a mech with a mech, initiate combat between them var/obj/item/toy/prize/P = I From 5f2016f1da0d689facd2a80cc43684a1e6f6f52e Mon Sep 17 00:00:00 2001 From: MrMelbert Date: Tue, 23 Jun 2020 20:39:17 -0500 Subject: [PATCH 007/196] cleans up some indents --- code/game/objects/items/toys.dm | 84 ++++++++++++++++----------------- 1 file changed, 42 insertions(+), 42 deletions(-) diff --git a/code/game/objects/items/toys.dm b/code/game/objects/items/toys.dm index fff5c8a50d1..5f224076e59 100644 --- a/code/game/objects/items/toys.dm +++ b/code/game/objects/items/toys.dm @@ -548,11 +548,11 @@ var/wants_to_battle = FALSE /// TRUE = in combat currently || FALSE = Not in combat var/in_combat = FALSE - /// The mech's health in battle + /// The mech's health in battle var/combat_health = 0 /// The mech's max combat health var/max_combat_health = 0 - /// TRUE = the special attack is charged || FALSE = not charged + /// TRUE = the special attack is charged || FALSE = not charged var/special_attack_charged = FALSE /// What type of special attack they use - SPECIAL_ATTACK_DAMAGE, SPECIAL_ATTACK_HEAL, SPECIAL_ATTACK_UTILITY, SPECIAL_ATTACK_OTHER var/special_attack_type = 0 @@ -717,16 +717,16 @@ * * Arguments * - * src: the defending toy, the toy that is hit by the attacker to start the battle - * attacker: the attacking toy, the toy in the attacker_controller's hands + * src: the defending toy, the toy that is hit by the attacker to start the battle + * attacker: the attacking toy, the toy in the attacker_controller's hands * attacker_controller: the user, the one who is holding the toys / controlling the fight - * opponent: optional arg used in Mech PvP battles: the other person who is taking part in the fight (controls src) + * opponent: optional arg used in Mech PvP battles: the other person who is taking part in the fight (controls src) */ /obj/item/toy/prize/proc/mechaBrawl(obj/item/toy/prize/attacker, mob/living/carbon/attacker_controller, mob/living/carbon/opponent) //A GOOD DAY FOR A SWELL BATTLE! attacker_controller.visible_message(" [attacker_controller.name] collides [attacker] with [src]! Looks like they're preparing for a brawl! ", \ - " You collide [attacker] into [src], sparking a fierce battle! ", \ - " You hear hard plastic smacking into hard plastic.", COMBAT_MESSAGE_RANGE) + " You collide [attacker] into [src], sparking a fierce battle! ", \ + " You hear hard plastic smacking into hard plastic.", COMBAT_MESSAGE_RANGE) /// Who's in control of the defender (src)? var/mob/living/carbon/src_controller = (opponent)? opponent : attacker_controller @@ -750,35 +750,35 @@ //if the two toys aren't next to each other, the battle ends if(!in_range(src, attacker)) attacker_controller.visible_message(" [attacker] and [src] separate, ending the battle. ", \ - " [attacker] and [src] separate, ending the battle. ") + " [attacker] and [src] separate, ending the battle. ") break //if the attacker_controller isn't next to the attacking toy (and doesn't have telekinesis), the battle ends if((!in_range(attacker, attacker_controller) && !(attacker_controller.dna.check_mutation(TK)))) attacker_controller.visible_message(" [attacker_controller.name] seperates from [attacker], ending the battle.", \ - " You separate from [attacker], ending the battle. ") + " You separate from [attacker], ending the battle. ") break //if it's PVP and the opponent is not next to the defending(src) toy (and doesn't have telekinesis), the battle ends if(opponent) if(!in_range(src, opponent) && !(opponent.dna.check_mutation(TK))) opponent.visible_message(" [opponent.name] seperates from [src], ending the battle.", \ - " You separate from [src], ending the battle. ") + " You separate from [src], ending the battle. ") break //if it's not PVP and the attacker_controller isn't next to the defending toy (and doesn't have telekinesis), the battle ends else if (!in_range(src, attacker_controller) && !(attacker_controller.dna.check_mutation(TK))) attacker_controller.visible_message(" [attacker_controller.name] seperates from [src] and [attacker], ending the battle.", \ - " You separate [attacker] and [src], ending the battle. ") + " You separate [attacker] and [src], ending the battle. ") break //before we do anything - deal with charged attacks if(special_attack_charged) src_controller.visible_message(" [src] unleashes its special attack!! ", \ - " You unleash [src]'s special attack! ") + " You unleash [src]'s special attack! ") specialAttackMove(attacker) else if(attacker.special_attack_charged) attacker_controller.visible_message(" [attacker] unleashes its special attack!! ", \ - " You unleash [attacker]'s special attack! ") + " You unleash [attacker]'s special attack! ") attacker.specialAttackMove(src) else //process the cooldowns @@ -793,19 +793,19 @@ if(attacker.special_attack_cooldown == 0 && attacker.combat_health <= round(attacker.max_combat_health/3)) //if health is less than 1/3 and special off CD, use it attacker.special_attack_charged = TRUE attacker_controller.visible_message(" [attacker] begins charging its special attack!! ", \ - " You begin charging [attacker]'s special attack! ") + " You begin charging [attacker]'s special attack! ") else //just attack attacker.SpinAnimation(5, 0) playsound(attacker, 'sound/mecha/mechstep.ogg', 30, TRUE) combat_health-- attacker_controller.visible_message(" [attacker] devastates [src]! ", \ - " You ram [attacker] into [src]! ", \ - " You hear hard plastic smacking hard plastic.", COMBAT_MESSAGE_RANGE) + " You ram [attacker] into [src]! ", \ + " You hear hard plastic smacking hard plastic.", COMBAT_MESSAGE_RANGE) if(prob(5)) combat_health-- playsound(src, 'sound/effects/meteorimpact.ogg', 20, TRUE) attacker_controller.visible_message(" ...and lands a CRIPPLING BLOW! ", \ - " ...and you land a CRIPPLING blow on [src]! ", null, COMBAT_MESSAGE_RANGE) + " ...and you land a CRIPPLING blow on [src]! ", null, COMBAT_MESSAGE_RANGE) if(4) //both lose attacker.SpinAnimation(5, 0) @@ -816,43 +816,43 @@ do_sparks(2, FALSE, attacker) if(prob(50)) attacker_controller.visible_message(" [attacker] and [src] clash dramatically, causing sparks to fly! ", \ - " [attacker] and [src] clash dramatically, causing sparks to fly! ", \ - " You hear hard plastic rubbing against hard plastic.", COMBAT_MESSAGE_RANGE) + " [attacker] and [src] clash dramatically, causing sparks to fly! ", \ + " You hear hard plastic rubbing against hard plastic.", COMBAT_MESSAGE_RANGE) else src_controller.visible_message(" [src] and [attacker] clash dramatically, causing sparks to fly! ", \ - " [src] and [attacker] clash dramatically, causing sparks to fly! ", \ - " You hear hard plastic rubbing against hard plastic.", COMBAT_MESSAGE_RANGE) + " [src] and [attacker] clash dramatically, causing sparks to fly! ", \ + " You hear hard plastic rubbing against hard plastic.", COMBAT_MESSAGE_RANGE) if(5) //both win playsound(attacker, 'sound/weapons/parry.ogg', 20, TRUE) if(prob(50)) attacker_controller.visible_message(" [src]'s attack deflects off of [attacker]. ", \ - " [src]'s attack deflects off of [attacker]. ", \ - " You hear hard plastic bouncing off hard plastic.", COMBAT_MESSAGE_RANGE) + " [src]'s attack deflects off of [attacker]. ", \ + " You hear hard plastic bouncing off hard plastic.", COMBAT_MESSAGE_RANGE) else src_controller.visible_message(" [attacker]'s attack deflects off of [src]. ", \ - " [attacker]'s attack deflects off of [src]. ", \ - " You hear hard plastic bouncing off hard plastic.", COMBAT_MESSAGE_RANGE) + " [attacker]'s attack deflects off of [src]. ", \ + " You hear hard plastic bouncing off hard plastic.", COMBAT_MESSAGE_RANGE) if(6 to 8) //defender wins if(special_attack_cooldown == 0 && combat_health <= round(max_combat_health/3)) //if health is less than 1/3 and special off CD, use it special_attack_charged = TRUE src_controller.visible_message(" [src] begins charging its special attack!! ", \ - " You begin charging [src]'s special attack! ") + " You begin charging [src]'s special attack! ") else //just attack SpinAnimation(5, 0) playsound(src, 'sound/mecha/mechstep.ogg', 30, TRUE) attacker.combat_health-- src_controller.visible_message(" [src] smashes [attacker]! ", \ - " You smash [src] into [attacker]! ", \ - " You hear hard plastic smashing hard plastic.", COMBAT_MESSAGE_RANGE) + " You smash [src] into [attacker]! ", \ + " You hear hard plastic smashing hard plastic.", COMBAT_MESSAGE_RANGE) if(prob(5)) attacker.combat_health-- playsound(attacker, 'sound/effects/meteorimpact.ogg', 20, TRUE) src_controller.visible_message(" ...and lands a CRIPPLING BLOW! ", \ - " ...and you land a CRIPPLING blow on [attacker]! ", null, COMBAT_MESSAGE_RANGE) + " ...and you land a CRIPPLING blow on [attacker]! ", null, COMBAT_MESSAGE_RANGE) else attacker_controller.visible_message(" [src] and [attacker] stand around awkwardly.", \ - " You don't know what to do next.") + " You don't know what to do next.") battle_length++ sleep(10) @@ -863,27 +863,27 @@ if(attacker.combat_health <= 0 && combat_health <= 0) //both lose playsound(src, 'sound/machines/warning-buzzer.ogg', 20, TRUE) attacker_controller.visible_message(" MUTUALLY ASSURED DESTRUCTION!! [src] and [attacker] both end up destroyed!", \ - " Both [src] and [attacker] are destroyed!") + " Both [src] and [attacker] are destroyed!") else if(attacker.combat_health <= 0) //src wins wins++ attacker.losses++ playsound(attacker, 'sound/effects/light_flicker.ogg', 20, TRUE) attacker_controller.visible_message(" [attacker] falls apart!", \ - " [attacker] falls apart!", null, COMBAT_MESSAGE_RANGE) + " [attacker] falls apart!", null, COMBAT_MESSAGE_RANGE) if(!quiet) say("[pick(winlines)]") src_controller.visible_message(" [src] destroys [attacker] and walks away victorious!", \ - " You raise up [src] victoriously over [attacker]!") + " You raise up [src] victoriously over [attacker]!") else if (combat_health <= 0) //attacker wins attacker.wins++ losses++ playsound(src, 'sound/effects/light_flicker.ogg', 20, TRUE) src_controller.visible_message(" [src] collapses!", \ - " [src] collapses!", null, COMBAT_MESSAGE_RANGE) + " [src] collapses!", null, COMBAT_MESSAGE_RANGE) if(!attacker.quiet) attacker.say("[pick(winlines)]") attacker_controller.visible_message(" [attacker] demolishes [src] and walks away victorious!", \ - " You raise up [attacker] proudly over [src]!") + " You raise up [attacker] proudly over [src]!") else //both win? if(!quiet) say("I WENT EASY ON YOU.") @@ -907,9 +907,9 @@ * * Arguments * - * user: the user who is initiating the battle - * attacker: optional arg for checking two mechs at once - * target: optional arg used in Mech PvP battles (if used, attacker is target's toy) + * user: the user who is initiating the battle + * attacker: optional arg for checking two mechs at once + * target: optional arg used in Mech PvP battles (if used, attacker is target's toy) */ /obj/item/toy/prize/proc/checkBattleStart(mob/living/carbon/user, obj/item/toy/prize/attacker, mob/living/carbon/target) if(attacker && attacker.in_combat) @@ -937,8 +937,8 @@ * Makes the toy shout their special attack cry and updates its cooldown. Then, does the special attack. * Arguments * - * src: the toy, doing its special move(not an arg but relevant) - * victim: the toy being hit by the special move + * src: the toy, doing its special move(not an arg but relevant) + * victim: the toy being hit by the special move */ /obj/item/toy/prize/proc/specialAttackMove(obj/item/toy/prize/victim) if(!quiet) @@ -968,8 +968,8 @@ * * This one is only for inheritance, each mech with an 'other' type move has their procs below. * Arguments - * src: the toy, doing its super special move(not an arg but relevant) - * victim: the toy being hit by the super special move (doesn't necessarily need to be used) + * src: the toy, doing its super special move(not an arg but relevant) + * victim: the toy being hit by the super special move (doesn't necessarily need to be used) */ /obj/item/toy/prize/proc/superSpecialAttack(obj/item/toy/prize/victim) visible_message(" [src] does a cool flip.") From 8b76c1f99a49fc98c6ae2d2474aa94abf3acdb34 Mon Sep 17 00:00:00 2001 From: MrMelbert Date: Tue, 23 Jun 2020 22:39:00 -0500 Subject: [PATCH 008/196] incapacitated people can't have epic fights --- code/game/objects/items/toys.dm | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/code/game/objects/items/toys.dm b/code/game/objects/items/toys.dm index 5f224076e59..94fd458d497 100644 --- a/code/game/objects/items/toys.dm +++ b/code/game/objects/items/toys.dm @@ -752,6 +752,9 @@ attacker_controller.visible_message(" [attacker] and [src] separate, ending the battle. ", \ " [attacker] and [src] separate, ending the battle. ") break + //dead men tell no tales, incapacitated men fight no fights + if(attacker_controller.incapacitated()) + break //if the attacker_controller isn't next to the attacking toy (and doesn't have telekinesis), the battle ends if((!in_range(attacker, attacker_controller) && !(attacker_controller.dna.check_mutation(TK)))) attacker_controller.visible_message(" [attacker_controller.name] seperates from [attacker], ending the battle.", \ @@ -759,6 +762,8 @@ break //if it's PVP and the opponent is not next to the defending(src) toy (and doesn't have telekinesis), the battle ends if(opponent) + if(opponent.incapacitated()) + break if(!in_range(src, opponent) && !(opponent.dna.check_mutation(TK))) opponent.visible_message(" [opponent.name] seperates from [src], ending the battle.", \ " You separate from [src], ending the battle. ") From e4297ae9a4f3ff452f872ae92f8b8f2e92230d35 Mon Sep 17 00:00:00 2001 From: MrMelbert Date: Tue, 23 Jun 2020 23:01:14 -0500 Subject: [PATCH 009/196] CONTRIBUTING.md compliance that i missed --- code/game/objects/items/toys.dm | 57 +++++++++++++++++---------------- 1 file changed, 29 insertions(+), 28 deletions(-) diff --git a/code/game/objects/items/toys.dm b/code/game/objects/items/toys.dm index 94fd458d497..2d45ee16c76 100644 --- a/code/game/objects/items/toys.dm +++ b/code/game/objects/items/toys.dm @@ -541,7 +541,7 @@ /// Timer when it'll be off cooldown var/timer = 0 /// Cooldown between play sessions - 8x longer after a battle - var/cooldown = 25 + var/cooldown = 1.5 SECONDS /// If it makes noise when played with var/quiet = FALSE /// TRUE = Offering battle to someone || FALSE = Not offering battle @@ -603,8 +603,8 @@ /obj/item/toy/prize/attackby(obj/item/I, mob/living/user) //I is user's toy if(istype(I, /obj/item/toy/prize)) //if you attack a mech with a mech, initiate combat between them var/obj/item/toy/prize/P = I - if(checkBattleStart(user, P)) - mechaBrawl(P, user) + if(check_battle_start(user, P)) + mecha_brawl(P, user) ..() /obj/item/toy/prize/attack(mob/living/carbon/human/target, mob/living/carbon/human/user) //src is user's toy @@ -615,18 +615,18 @@ if(wants_to_battle) //prevent spamming someone with offers to_chat(user, "You already are offering battle to someone!") return - if(!checkBattleStart(user)) //if the user's mech isn't ready, don't bother checking + if(!check_battle_start(user)) //if the user's mech isn't ready, don't bother checking return for(var/obj/item/I in target.held_items) if(istype(I, /obj/item/toy/prize)) //if you attack someone with a mech who's also holding a mech, offer to battle them var/obj/item/toy/prize/P = I - if(!P.checkBattleStart(target, null, user)) //check if the attacker mech is ready + if(!P.check_battle_start(target, null, user)) //check if the attacker mech is ready break //slap them with the metaphorical white glove if(P.wants_to_battle) //if the target mech wants to battle, initiate the battle from their POV - mechaBrawl(P, target, user) //P = challenger's mech / SRC = defender's mech / user = defender / target = challenger + mecha_brawl(P, target, user) //P = challenger's mech / SRC = defender's mech / user = defender / target = challenger P.wants_to_battle = FALSE return @@ -634,7 +634,7 @@ to_chat(user, "You offer battle to [target.name]!") to_chat(target, "[user.name] wants to battle with [user.p_their()] [name]! Attack them with a toy mech to initiate combat.") wants_to_battle = TRUE - addtimer(CALLBACK(src, .proc/withdrawOffer, user), 60) + addtimer(CALLBACK(src, .proc/withdraw_offer, user), 6 SECONDS) return ..() @@ -646,7 +646,7 @@ * Arguments * user: the user wanting to do battle */ -/obj/item/toy/prize/proc/withdrawOffer(mob/living/carbon/user) +/obj/item/toy/prize/proc/withdraw_offer(mob/living/carbon/user) if(wants_to_battle) wants_to_battle = FALSE to_chat(user, "You get the feeling they don't want to battle.") @@ -659,28 +659,28 @@ user.visible_message("[user] begins a fight [user.p_they()] can't win with [src]! It looks like [user.p_theyre()] trying to commit suicide!") in_combat = TRUE - sleep(20) + sleep(1.5 SECONDS) SpinAnimation(5, 0) user.adjustBruteLoss(20) user.adjustStaminaLoss(40) - sleep(10) + sleep(1 SECONDS) user.SpinAnimation(5, 0) combat_health-- //we scratched it! - sleep(10) + sleep(1 SECONDS) SpinAnimation(5, 0) user.adjustBruteLoss(15) user.adjustStaminaLoss(30) - sleep(10) + sleep(1 SECONDS) SpinAnimation(5, 0) user.adjustBruteLoss(15) user.adjustStaminaLoss(30) - sleep(10) + sleep(1 SECONDS) if(!quiet) say(special_attack_cry + "!!") user.emote("scream") else SpinAnimation(5, 0) - sleep(15) + sleep(1.5 SECONDS) if(!in_range(src, user)) //run away? coward say("PATHETIC") return SHAME @@ -722,7 +722,7 @@ * attacker_controller: the user, the one who is holding the toys / controlling the fight * opponent: optional arg used in Mech PvP battles: the other person who is taking part in the fight (controls src) */ -/obj/item/toy/prize/proc/mechaBrawl(obj/item/toy/prize/attacker, mob/living/carbon/attacker_controller, mob/living/carbon/opponent) +/obj/item/toy/prize/proc/mecha_brawl(obj/item/toy/prize/attacker, mob/living/carbon/attacker_controller, mob/living/carbon/opponent) //A GOOD DAY FOR A SWELL BATTLE! attacker_controller.visible_message(" [attacker_controller.name] collides [attacker] with [src]! Looks like they're preparing for a brawl! ", \ " You collide [attacker] into [src], sparking a fierce battle! ", \ @@ -736,10 +736,11 @@ in_combat = TRUE attacker.in_combat = TRUE - timer = world.time + cooldown*8 - attacker.timer = world.time + attacker.cooldown*8 + //1.5 second cooldown * 20 = 30 second cooldown after a fight + timer = world.time + cooldown*20 + attacker.timer = world.time + attacker.cooldown*20 - sleep(15) + sleep(1.5 SECONDS) //--THE BATTLE BEGINS-- while(combat_health > 0 && attacker.combat_health > 0 && battle_length < MAX_BATTLE_LENGTH) @@ -779,12 +780,12 @@ if(special_attack_charged) src_controller.visible_message(" [src] unleashes its special attack!! ", \ " You unleash [src]'s special attack! ") - specialAttackMove(attacker) + special_attack_move(attacker) else if(attacker.special_attack_charged) attacker_controller.visible_message(" [attacker] unleashes its special attack!! ", \ " You unleash [attacker]'s special attack! ") - attacker.specialAttackMove(src) + attacker.special_attack_move(src) else //process the cooldowns if(special_attack_cooldown > 0) @@ -860,7 +861,7 @@ " You don't know what to do next.") battle_length++ - sleep(10) + sleep(1 SECONDS) /// Lines chosen for the winning mech var/list/winlines = list("YOU'RE NOTHING BUT SCRAP!", "I'LL YIELD TO NONE!", "GLORY IS MINE!", "AN EASY FIGHT.", "YOU SHOULD HAVE NEVER FACED ME.") @@ -916,7 +917,7 @@ * attacker: optional arg for checking two mechs at once * target: optional arg used in Mech PvP battles (if used, attacker is target's toy) */ -/obj/item/toy/prize/proc/checkBattleStart(mob/living/carbon/user, obj/item/toy/prize/attacker, mob/living/carbon/target) +/obj/item/toy/prize/proc/check_battle_start(mob/living/carbon/user, obj/item/toy/prize/attacker, mob/living/carbon/target) if(attacker && attacker.in_combat) to_chat(user, "[target?target.p_their() : "Your" ] [attacker.name] is in combat.") target?.to_chat(target, "Your [attacker.name] is in combat.") @@ -945,7 +946,7 @@ * src: the toy, doing its special move(not an arg but relevant) * victim: the toy being hit by the special move */ -/obj/item/toy/prize/proc/specialAttackMove(obj/item/toy/prize/victim) +/obj/item/toy/prize/proc/special_attack_move(obj/item/toy/prize/victim) if(!quiet) say(special_attack_cry + "!!") @@ -964,7 +965,7 @@ combat_health++ playsound(src, 'sound/mecha/mechmove01.ogg', 30, TRUE) if(SPECIAL_ATTACK_OTHER) //other - superSpecialAttack(victim) + super_special_attack(victim) else say("I FORGOT MY SPECIAL ATTACK...") @@ -976,7 +977,7 @@ * src: the toy, doing its super special move(not an arg but relevant) * victim: the toy being hit by the super special move (doesn't necessarily need to be used) */ -/obj/item/toy/prize/proc/superSpecialAttack(obj/item/toy/prize/victim) +/obj/item/toy/prize/proc/super_special_attack(obj/item/toy/prize/victim) visible_message(" [src] does a cool flip.") /obj/item/toy/prize/ripley @@ -1003,7 +1004,7 @@ special_attack_type_message = "instantly destroys the opposing mech if its health is less than this mech's health." special_attack_cry = "KILLER CLAMP" -/obj/item/toy/prize/deathripley/superSpecialAttack(obj/item/toy/prize/victim) +/obj/item/toy/prize/deathripley/super_special_attack(obj/item/toy/prize/victim) playsound(src, 'sound/weapons/sonic_jackhammer.ogg', 20, TRUE) if(victim.combat_health < combat_health) //Instantly kills the other mech if it's health is below our's. say("EXECUTE!!") @@ -1036,7 +1037,7 @@ special_attack_type_message = "puts the opposing mech's special move on cooldown and heals this mech." special_attack_cry = "MEGA HORN" -/obj/item/toy/prize/honk/superSpecialAttack(obj/item/toy/prize/victim) +/obj/item/toy/prize/honk/super_special_attack(obj/item/toy/prize/victim) playsound(src, 'sound/machines/honkbot_evil_laugh.ogg', 20, TRUE) victim.special_attack_cooldown = 3 //Adds cooldown to the other mech and gives a minor self heal combat_health++ @@ -1091,7 +1092,7 @@ special_attack_type_message = "has a lower cooldown than normal special moves and deals damage." special_attack_cry = "*wave" -/obj/item/toy/prize/reticence/superSpecialAttack(obj/item/toy/prize/victim) +/obj/item/toy/prize/reticence/super_special_attack(obj/item/toy/prize/victim) special_attack_cooldown-- //Has a lower cooldown... victim.special_attack_cooldown++ //and increases the opponent's cooldown by 1... victim.combat_health-- //and some free damage. From ce2ad1628253974f0af3827ee696b407c8cf47a0 Mon Sep 17 00:00:00 2001 From: MrMelbert Date: Wed, 24 Jun 2020 02:18:52 -0500 Subject: [PATCH 010/196] fixes doc, suicide, adds a sleep proc for ease --- code/game/objects/items/toys.dm | 243 ++++++++++++++++++-------------- 1 file changed, 141 insertions(+), 102 deletions(-) diff --git a/code/game/objects/items/toys.dm b/code/game/objects/items/toys.dm index 2d45ee16c76..32d4b534f9d 100644 --- a/code/game/objects/items/toys.dm +++ b/code/game/objects/items/toys.dm @@ -527,9 +527,9 @@ qdel(src) -/* - * Mech prizes + MECHA COMBAT!! - */ +/** + * Mech prizes + MECHA COMBAT!! + */ /obj/item/toy/prize icon = 'icons/obj/toy.dmi' icon_state = "ripleytoy" @@ -540,7 +540,7 @@ w_class = WEIGHT_CLASS_SMALL /// Timer when it'll be off cooldown var/timer = 0 - /// Cooldown between play sessions - 8x longer after a battle + /// Cooldown between play sessions - 20x longer after a battle var/cooldown = 1.5 SECONDS /// If it makes noise when played with var/quiet = FALSE @@ -583,6 +583,62 @@ else special_attack_type_message = "a mystery move, even I don't know." +/** + * this proc combines "sleep" while also checking for if the battle should continue + * + * this goes through some of the checks - the toys need to be next to each other to fight! + * if it's player vs themself: They need to be able to "control" both mechs (either must be adjacent or using TK) + * if it's player vs player: Both players need to be able to "control" their mechs (either must be adjacent or using TK) + * if it's player vs mech (suicide): the mech needs to be in range of the player + * if all the checks are TRUE, it does the sleeps, and returns TRUE. Otherwise, it returns FALSE. + * Arguments: + * * delay - the amount of time the sleep at the end of the check will sleep for + * * attacker: - the attacking toy in the battle. + * * attacker_controller - the controller of the attacking toy. there should ALWAYS be an attacker_controller + * * opponent - (optional) the defender controller in the battle, for PvP + */ +/obj/item/toy/prize/proc/combat_sleep(var/delay, obj/item/toy/prize/attacker, mob/living/carbon/attacker_controller, mob/living/carbon/opponent) + if(!attacker_controller) + return FALSE + + if(!attacker) //if there's no attacker, then attacker_controller IS the attacker + if(!in_range(src, attacker_controller)) + attacker_controller.visible_message("[attacker_controller] is running from [src]! The coward!") + return FALSE + else // if there's an attacker, we can procede as normal + if(!in_range(src, attacker)) //and the two toys aren't next to each other, the battle ends + attacker_controller.visible_message(" [attacker] and [src] separate, ending the battle. ", \ + " [attacker] and [src] separate, ending the battle. ") + return FALSE + + //dead men tell no tales, incapacitated men fight no fights + if(attacker_controller.incapacitated()) + return FALSE + //if the attacker_controller isn't next to the attacking toy (and doesn't have telekinesis), the battle ends + if(!in_range(attacker, attacker_controller) && !(attacker_controller.dna.check_mutation(TK))) + attacker_controller.visible_message(" [attacker_controller.name] seperates from [attacker], ending the battle.", \ + " You separate from [attacker], ending the battle. ") + return FALSE + + //if it's PVP and the opponent is not next to the defending(src) toy (and doesn't have telekinesis), the battle ends + if(opponent) + if(opponent.incapacitated()) + return FALSE + if(!in_range(src, opponent) && !(opponent.dna.check_mutation(TK))) + opponent.visible_message(" [opponent.name] seperates from [src], ending the battle.", \ + " You separate from [src], ending the battle. ") + return FALSE + //if it's not PVP and the attacker_controller isn't next to the defending toy (and doesn't have telekinesis), the battle ends + else + if (!in_range(src, attacker_controller) && !(attacker_controller.dna.check_mutation(TK))) + attacker_controller.visible_message(" [attacker_controller.name] seperates from [src] and [attacker], ending the battle.", \ + " You separate [attacker] and [src], ending the battle. ") + return FALSE + + //if all that is good, then we can sleep peacefully + sleep(delay) + return TRUE + //all credit to skasi for toy mech fun ideas /obj/item/toy/prize/attack_self(mob/user) if(timer < world.time) @@ -600,14 +656,20 @@ if(loc == user) attack_self(user) -/obj/item/toy/prize/attackby(obj/item/I, mob/living/user) //I is user's toy - if(istype(I, /obj/item/toy/prize)) //if you attack a mech with a mech, initiate combat between them - var/obj/item/toy/prize/P = I +/** + * If you attack a mech with a mech, initiate combat between them + */ +/obj/item/toy/prize/attackby(obj/item/user_toy, mob/living/user) + if(istype(user_toy, /obj/item/toy/prize)) + var/obj/item/toy/prize/P = user_toy if(check_battle_start(user, P)) mecha_brawl(P, user) ..() -/obj/item/toy/prize/attack(mob/living/carbon/human/target, mob/living/carbon/human/user) //src is user's toy +/** + * Attack is called from the user's toy, aimed at target(another human), checking for target's toy. + */ +/obj/item/toy/prize/attack(mob/living/carbon/human/target, mob/living/carbon/human/user) if(target == user) to_chat(user, "Target another toy mech if you want to start a battle with yourself.") return @@ -626,7 +688,7 @@ //slap them with the metaphorical white glove if(P.wants_to_battle) //if the target mech wants to battle, initiate the battle from their POV - mecha_brawl(P, target, user) //P = challenger's mech / SRC = defender's mech / user = defender / target = challenger + mecha_brawl(P, target, user) //P = defender's mech / SRC = attacker's mech / target = defender / user = attacker P.wants_to_battle = FALSE return @@ -639,18 +701,30 @@ ..() +/** + * Overrides attack_tk - Sorry, you have to be face to face to initiate a battle, it's good sportsmanship + */ +/obj/item/toy/prize/attack_tk(mob/user) + if(timer < world.time) + to_chat(user, "You telekinetically play with [src].") + timer = world.time + cooldown + if(!quiet) + playsound(user, 'sound/mecha/mechstep.ogg', 20, TRUE) + /** * Resets the request for battle. * * For use in a timer, this proc resets the wants_to_battle variable after a short period. - * Arguments - * user: the user wanting to do battle + * Arguments: + * * user - the user wanting to do battle */ /obj/item/toy/prize/proc/withdraw_offer(mob/living/carbon/user) if(wants_to_battle) wants_to_battle = FALSE to_chat(user, "You get the feeling they don't want to battle.") - +/** + * Starts a battle, toy mech vs player. Player... doesn't win. + */ /obj/item/toy/prize/suicide_act(mob/living/carbon/user) if(in_combat) to_chat(user, "[src] is in battle, let it finish first.") @@ -660,30 +734,29 @@ in_combat = TRUE sleep(1.5 SECONDS) - SpinAnimation(5, 0) - user.adjustBruteLoss(20) - user.adjustStaminaLoss(40) - sleep(1 SECONDS) - user.SpinAnimation(5, 0) - combat_health-- //we scratched it! - sleep(1 SECONDS) - SpinAnimation(5, 0) - user.adjustBruteLoss(15) - user.adjustStaminaLoss(30) - sleep(1 SECONDS) - SpinAnimation(5, 0) - user.adjustBruteLoss(15) - user.adjustStaminaLoss(30) - sleep(1 SECONDS) - if(!quiet) - say(special_attack_cry + "!!") - user.emote("scream") - else - SpinAnimation(5, 0) - sleep(1.5 SECONDS) - if(!in_range(src, user)) //run away? coward - say("PATHETIC") - return SHAME + for(var/i in 1 to 4) + switch(i) + if(1) + SpinAnimation(5, 0) + user.adjustBruteLoss(25) + user.adjustStaminaLoss(50) + if(2) + user.SpinAnimation(5, 0) + combat_health-- //we scratched it! + if(3) + SpinAnimation(5, 0) + user.adjustBruteLoss(25) + user.adjustStaminaLoss(50) + if(4) + say(special_attack_cry + "!!") + user.emote("scream") + user.adjustStaminaLoss(25) + + if(!combat_sleep(1 SECONDS, null, user)) + say("PATHETIC.") + combat_health = max_combat_health + in_combat = FALSE + return SHAME user.adjustBruteLoss(450) @@ -707,20 +780,25 @@ if(wins || losses) . += "This toy has [wins] wins, and [losses] losses." +/** + * Override the say proc if they're mute + */ +/obj/item/toy/prize/say() + if(!quiet) + . = ..() + /** * The 'master' proc of the mech battle. Processes the entire battle's events and makes sure it start and finishes correctly. * + * src is the defending toy, and the battle proc is called on it to begin the battle. * After going through a few checks at the beginning to ensure the battle can start properly, the battle begins a loop that lasts * until either toy has no more health. During this loop, it also ensures the mechs stay in combat range of each other. * It will then randomly decide attacks for each toy, occasionally making one or the other use their special attack. * When either mech has no more health, the loop ends, and it displays the victor and the loser while updating their stats and resetting them. - * - * Arguments - * - * src: the defending toy, the toy that is hit by the attacker to start the battle - * attacker: the attacking toy, the toy in the attacker_controller's hands - * attacker_controller: the user, the one who is holding the toys / controlling the fight - * opponent: optional arg used in Mech PvP battles: the other person who is taking part in the fight (controls src) + * Arguments: + * * attacker - the attacking toy, the toy in the attacker_controller's hands + * * attacker_controller - the user, the one who is holding the toys / controlling the fight + * * opponent - optional arg used in Mech PvP battles: the other person who is taking part in the fight (controls src) */ /obj/item/toy/prize/proc/mecha_brawl(obj/item/toy/prize/attacker, mob/living/carbon/attacker_controller, mob/living/carbon/opponent) //A GOOD DAY FOR A SWELL BATTLE! @@ -740,41 +818,11 @@ timer = world.time + cooldown*20 attacker.timer = world.time + attacker.cooldown*20 - sleep(1.5 SECONDS) + sleep(1 SECONDS) //--THE BATTLE BEGINS-- while(combat_health > 0 && attacker.combat_health > 0 && battle_length < MAX_BATTLE_LENGTH) - - //the toys need to be next to each other to fight! - //if it's player vs themself: They need to be able to "control" both mechs (either adjacent or using TK) - //if it's player vs player: Both players need to be able to "control" their mechs (either adjacent or using TK) - - //if the two toys aren't next to each other, the battle ends - if(!in_range(src, attacker)) - attacker_controller.visible_message(" [attacker] and [src] separate, ending the battle. ", \ - " [attacker] and [src] separate, ending the battle. ") - break - //dead men tell no tales, incapacitated men fight no fights - if(attacker_controller.incapacitated()) - break - //if the attacker_controller isn't next to the attacking toy (and doesn't have telekinesis), the battle ends - if((!in_range(attacker, attacker_controller) && !(attacker_controller.dna.check_mutation(TK)))) - attacker_controller.visible_message(" [attacker_controller.name] seperates from [attacker], ending the battle.", \ - " You separate from [attacker], ending the battle. ") - break - //if it's PVP and the opponent is not next to the defending(src) toy (and doesn't have telekinesis), the battle ends - if(opponent) - if(opponent.incapacitated()) - break - if(!in_range(src, opponent) && !(opponent.dna.check_mutation(TK))) - opponent.visible_message(" [opponent.name] seperates from [src], ending the battle.", \ - " You separate from [src], ending the battle. ") - break - //if it's not PVP and the attacker_controller isn't next to the defending toy (and doesn't have telekinesis), the battle ends - else - if (!in_range(src, attacker_controller) && !(attacker_controller.dna.check_mutation(TK))) - attacker_controller.visible_message(" [attacker_controller.name] seperates from [src] and [attacker], ending the battle.", \ - " You separate [attacker] and [src], ending the battle. ") - break + if(!combat_sleep(0.5 SECONDS, attacker, attacker_controller, opponent)) //combat_sleep checks everything we need to have checked for combat to continue + break //before we do anything - deal with charged attacks if(special_attack_charged) @@ -861,10 +909,10 @@ " You don't know what to do next.") battle_length++ - sleep(1 SECONDS) + sleep(0.5 SECONDS) /// Lines chosen for the winning mech - var/list/winlines = list("YOU'RE NOTHING BUT SCRAP!", "I'LL YIELD TO NONE!", "GLORY IS MINE!", "AN EASY FIGHT.", "YOU SHOULD HAVE NEVER FACED ME.") + var/list/winlines = list("YOU'RE NOTHING BUT SCRAP!", "I'LL YIELD TO NONE!", "GLORY IS MINE!", "AN EASY FIGHT.", "YOU SHOULD HAVE NEVER FACED ME.", "ROCKED AND SOCKED.") if(attacker.combat_health <= 0 && combat_health <= 0) //both lose playsound(src, 'sound/machines/warning-buzzer.ogg', 20, TRUE) @@ -876,8 +924,7 @@ playsound(attacker, 'sound/effects/light_flicker.ogg', 20, TRUE) attacker_controller.visible_message(" [attacker] falls apart!", \ " [attacker] falls apart!", null, COMBAT_MESSAGE_RANGE) - if(!quiet) - say("[pick(winlines)]") + say("[pick(winlines)]") src_controller.visible_message(" [src] destroys [attacker] and walks away victorious!", \ " You raise up [src] victoriously over [attacker]!") else if (combat_health <= 0) //attacker wins @@ -886,15 +933,13 @@ playsound(src, 'sound/effects/light_flicker.ogg', 20, TRUE) src_controller.visible_message(" [src] collapses!", \ " [src] collapses!", null, COMBAT_MESSAGE_RANGE) - if(!attacker.quiet) - attacker.say("[pick(winlines)]") + attacker.say("[pick(winlines)]") attacker_controller.visible_message(" [attacker] demolishes [src] and walks away victorious!", \ " You raise up [attacker] proudly over [src]!") else //both win? - if(!quiet) - say("I WENT EASY ON YOU.") - if(!attacker.quiet) - attacker.say("OF COURSE YOU DID.") + say("NEXT TIME.") + //don't want to make this a one sided conversation + quiet? attacker.say("I WENT EASY ON YOU.") : attacker.say("OF COURSE.") in_combat = FALSE attacker.in_combat = FALSE @@ -910,12 +955,10 @@ * Both SRC and attacker (if attacker is included) timers are checked if they're on cooldown, and * both SRC and attacker (if attacker is included) are checked if they are in combat already. * If any of the above are true, the proc returns FALSE and sends a message to user (and target, if included) otherwise, it returns TRUE - * - * Arguments - * - * user: the user who is initiating the battle - * attacker: optional arg for checking two mechs at once - * target: optional arg used in Mech PvP battles (if used, attacker is target's toy) + * Arguments: + * * user: the user who is initiating the battle + * * attacker: optional arg for checking two mechs at once + * * target: optional arg used in Mech PvP battles (if used, attacker is target's toy) */ /obj/item/toy/prize/proc/check_battle_start(mob/living/carbon/user, obj/item/toy/prize/attacker, mob/living/carbon/target) if(attacker && attacker.in_combat) @@ -941,14 +984,11 @@ * Processes any special attack moves that happen in the battle (called in the mechaBattle proc). * * Makes the toy shout their special attack cry and updates its cooldown. Then, does the special attack. - * Arguments - * - * src: the toy, doing its special move(not an arg but relevant) - * victim: the toy being hit by the special move + * Arguments: + * * victim - the toy being hit by the special move */ /obj/item/toy/prize/proc/special_attack_move(obj/item/toy/prize/victim) - if(!quiet) - say(special_attack_cry + "!!") + say(special_attack_cry + "!!") special_attack_charged = FALSE special_attack_cooldown = 3 @@ -973,9 +1013,8 @@ * Base proc for 'other' special attack moves. * * This one is only for inheritance, each mech with an 'other' type move has their procs below. - * Arguments - * src: the toy, doing its super special move(not an arg but relevant) - * victim: the toy being hit by the super special move (doesn't necessarily need to be used) + * Arguments: + * * victim - the toy being hit by the super special move (doesn't necessarily need to be used) */ /obj/item/toy/prize/proc/super_special_attack(obj/item/toy/prize/victim) visible_message(" [src] does a cool flip.") @@ -1089,7 +1128,7 @@ quiet = TRUE max_combat_health = 4 special_attack_type = SPECIAL_ATTACK_OTHER - special_attack_type_message = "has a lower cooldown than normal special moves and deals damage." + special_attack_type_message = "has a lower cooldown than normal special moves, increases the opponent's cooldown, and deals damage." special_attack_cry = "*wave" /obj/item/toy/prize/reticence/super_special_attack(obj/item/toy/prize/victim) From 4fdb1e108cfe145bf582cd157ce69df2dd754456 Mon Sep 17 00:00:00 2001 From: MrMelbert Date: Wed, 24 Jun 2020 02:43:23 -0500 Subject: [PATCH 011/196] fixes a minor sin + makes it pretty --- code/game/objects/items/toys.dm | 48 ++++++++++++++++----------------- 1 file changed, 23 insertions(+), 25 deletions(-) diff --git a/code/game/objects/items/toys.dm b/code/game/objects/items/toys.dm index 32d4b534f9d..7021b5b2860 100644 --- a/code/game/objects/items/toys.dm +++ b/code/game/objects/items/toys.dm @@ -584,18 +584,18 @@ special_attack_type_message = "a mystery move, even I don't know." /** - * this proc combines "sleep" while also checking for if the battle should continue + * this proc combines "sleep" while also checking for if the battle should continue * - * this goes through some of the checks - the toys need to be next to each other to fight! - * if it's player vs themself: They need to be able to "control" both mechs (either must be adjacent or using TK) - * if it's player vs player: Both players need to be able to "control" their mechs (either must be adjacent or using TK) - * if it's player vs mech (suicide): the mech needs to be in range of the player - * if all the checks are TRUE, it does the sleeps, and returns TRUE. Otherwise, it returns FALSE. - * Arguments: - * * delay - the amount of time the sleep at the end of the check will sleep for - * * attacker: - the attacking toy in the battle. - * * attacker_controller - the controller of the attacking toy. there should ALWAYS be an attacker_controller - * * opponent - (optional) the defender controller in the battle, for PvP + * this goes through some of the checks - the toys need to be next to each other to fight! + * if it's player vs themself: They need to be able to "control" both mechs (either must be adjacent or using TK) + * if it's player vs player: Both players need to be able to "control" their mechs (either must be adjacent or using TK) + * if it's player vs mech (suicide): the mech needs to be in range of the player + * if all the checks are TRUE, it does the sleeps, and returns TRUE. Otherwise, it returns FALSE. + * Arguments: + * * delay - the amount of time the sleep at the end of the check will sleep for + * * attacker - the attacking toy in the battle. + * * attacker_controller - the controller of the attacking toy. there should ALWAYS be an attacker_controller + * * opponent - (optional) the defender controller in the battle, for PvP */ /obj/item/toy/prize/proc/combat_sleep(var/delay, obj/item/toy/prize/attacker, mob/living/carbon/attacker_controller, mob/living/carbon/opponent) if(!attacker_controller) @@ -736,20 +736,17 @@ sleep(1.5 SECONDS) for(var/i in 1 to 4) switch(i) - if(1) + if(1, 3) SpinAnimation(5, 0) + playsound(src, 'sound/mecha/mechstep.ogg', 30, TRUE) user.adjustBruteLoss(25) user.adjustStaminaLoss(50) if(2) user.SpinAnimation(5, 0) + playsound(user, 'sound/weapons/smash.ogg', 20, TRUE) combat_health-- //we scratched it! - if(3) - SpinAnimation(5, 0) - user.adjustBruteLoss(25) - user.adjustStaminaLoss(50) if(4) say(special_attack_cry + "!!") - user.emote("scream") user.adjustStaminaLoss(25) if(!combat_sleep(1 SECONDS, null, user)) @@ -758,6 +755,7 @@ in_combat = FALSE return SHAME + sleep(0.5 SECONDS) user.adjustBruteLoss(450) in_combat = FALSE @@ -796,9 +794,9 @@ * It will then randomly decide attacks for each toy, occasionally making one or the other use their special attack. * When either mech has no more health, the loop ends, and it displays the victor and the loser while updating their stats and resetting them. * Arguments: - * * attacker - the attacking toy, the toy in the attacker_controller's hands - * * attacker_controller - the user, the one who is holding the toys / controlling the fight - * * opponent - optional arg used in Mech PvP battles: the other person who is taking part in the fight (controls src) + * * attacker - the attacking toy, the toy in the attacker_controller's hands + * * attacker_controller - the user, the one who is holding the toys / controlling the fight + * * opponent - optional arg used in Mech PvP battles: the other person who is taking part in the fight (controls src) */ /obj/item/toy/prize/proc/mecha_brawl(obj/item/toy/prize/attacker, mob/living/carbon/attacker_controller, mob/living/carbon/opponent) //A GOOD DAY FOR A SWELL BATTLE! @@ -956,9 +954,9 @@ * both SRC and attacker (if attacker is included) are checked if they are in combat already. * If any of the above are true, the proc returns FALSE and sends a message to user (and target, if included) otherwise, it returns TRUE * Arguments: - * * user: the user who is initiating the battle - * * attacker: optional arg for checking two mechs at once - * * target: optional arg used in Mech PvP battles (if used, attacker is target's toy) + * * user: the user who is initiating the battle + * * attacker: optional arg for checking two mechs at once + * * target: optional arg used in Mech PvP battles (if used, attacker is target's toy) */ /obj/item/toy/prize/proc/check_battle_start(mob/living/carbon/user, obj/item/toy/prize/attacker, mob/living/carbon/target) if(attacker && attacker.in_combat) @@ -985,7 +983,7 @@ * * Makes the toy shout their special attack cry and updates its cooldown. Then, does the special attack. * Arguments: - * * victim - the toy being hit by the special move + * * victim - the toy being hit by the special move */ /obj/item/toy/prize/proc/special_attack_move(obj/item/toy/prize/victim) say(special_attack_cry + "!!") @@ -1014,7 +1012,7 @@ * * This one is only for inheritance, each mech with an 'other' type move has their procs below. * Arguments: - * * victim - the toy being hit by the super special move (doesn't necessarily need to be used) + * * victim - the toy being hit by the super special move (doesn't necessarily need to be used) */ /obj/item/toy/prize/proc/super_special_attack(obj/item/toy/prize/victim) visible_message(" [src] does a cool flip.") From b35ae6d2a234976920037c5ae7cbb46ae1138f1d Mon Sep 17 00:00:00 2001 From: MrMelbert Date: Wed, 24 Jun 2020 12:17:40 -0500 Subject: [PATCH 012/196] moved file --- code/game/objects/items/toy_mechs.dm | 630 ++++++++++++++++++++++++++ code/game/objects/items/toys.dm | 632 --------------------------- tgstation.dme | 1 + 3 files changed, 631 insertions(+), 632 deletions(-) create mode 100644 code/game/objects/items/toy_mechs.dm diff --git a/code/game/objects/items/toy_mechs.dm b/code/game/objects/items/toy_mechs.dm new file mode 100644 index 00000000000..7ede120afb9 --- /dev/null +++ b/code/game/objects/items/toy_mechs.dm @@ -0,0 +1,630 @@ +/** + * Mech prizes + MECHA COMBAT!! + */ + +/// Mech battle special attack types. +#define SPECIAL_ATTACK_HEAL 1 +#define SPECIAL_ATTACK_DAMAGE 2 +#define SPECIAL_ATTACK_UTILITY 3 +#define SPECIAL_ATTACK_OTHER 4 + +/// Max length of a mech battle +#define MAX_BATTLE_LENGTH 50 + +/obj/item/toy/prize + icon = 'icons/obj/toy.dmi' + icon_state = "ripleytoy" + verb_say = "beeps" + verb_ask = "beeps" + verb_exclaim = "beeps" + verb_yell = "beeps" + w_class = WEIGHT_CLASS_SMALL + /// Timer when it'll be off cooldown + var/timer = 0 + /// Cooldown between play sessions - 20x longer after a battle + var/cooldown = 1.5 SECONDS + /// If it makes noise when played with + var/quiet = FALSE + /// TRUE = Offering battle to someone || FALSE = Not offering battle + var/wants_to_battle = FALSE + /// TRUE = in combat currently || FALSE = Not in combat + var/in_combat = FALSE + /// The mech's health in battle + var/combat_health = 0 + /// The mech's max combat health + var/max_combat_health = 0 + /// TRUE = the special attack is charged || FALSE = not charged + var/special_attack_charged = FALSE + /// What type of special attack they use - SPECIAL_ATTACK_DAMAGE, SPECIAL_ATTACK_HEAL, SPECIAL_ATTACK_UTILITY, SPECIAL_ATTACK_OTHER + var/special_attack_type = 0 + /// What message their special move gets on examining + var/special_attack_type_message = "" + /// The battlecry when using the special attack + var/special_attack_cry = "*flip" + /// Current cooldown of their special attack + var/special_attack_cooldown = 0 + /// This mech's win count in combat + var/wins = 0 + /// ...And their loss count in combat + var/losses = 0 + +/obj/item/toy/prize/Initialize() + . = ..() + desc = "Mini-Mecha action figure! Collect them all! Attack your friends or another mech with one to initiate epic mech combat! [desc]." + combat_health = max_combat_health + switch(special_attack_type) + if(SPECIAL_ATTACK_DAMAGE) + special_attack_type_message = "an aggressive move, which deals bonus damage." + if(SPECIAL_ATTACK_HEAL) + special_attack_type_message = "a defensive move, which grants bonus healing." + if(SPECIAL_ATTACK_UTILITY) + special_attack_type_message = "a utility move, which heals the user and damages the opponent." + if(SPECIAL_ATTACK_OTHER) + special_attack_type_message = "a special move, which [special_attack_type_message]" + else + special_attack_type_message = "a mystery move, even I don't know." + +/** + * this proc combines "sleep" while also checking for if the battle should continue + * + * this goes through some of the checks - the toys need to be next to each other to fight! + * if it's player vs themself: They need to be able to "control" both mechs (either must be adjacent or using TK) + * if it's player vs player: Both players need to be able to "control" their mechs (either must be adjacent or using TK) + * if it's player vs mech (suicide): the mech needs to be in range of the player + * if all the checks are TRUE, it does the sleeps, and returns TRUE. Otherwise, it returns FALSE. + * Arguments: + * * delay - the amount of time the sleep at the end of the check will sleep for + * * attacker - the attacking toy in the battle. + * * attacker_controller - the controller of the attacking toy. there should ALWAYS be an attacker_controller + * * opponent - (optional) the defender controller in the battle, for PvP + */ +/obj/item/toy/prize/proc/combat_sleep(var/delay, obj/item/toy/prize/attacker, mob/living/carbon/attacker_controller, mob/living/carbon/opponent) + if(!attacker_controller) + return FALSE + + if(!attacker) //if there's no attacker, then attacker_controller IS the attacker + if(!in_range(src, attacker_controller)) + attacker_controller.visible_message("[attacker_controller] is running from [src]! The coward!") + return FALSE + else // if there's an attacker, we can procede as normal + if(!in_range(src, attacker)) //and the two toys aren't next to each other, the battle ends + attacker_controller.visible_message(" [attacker] and [src] separate, ending the battle. ", \ + " [attacker] and [src] separate, ending the battle. ") + return FALSE + + //dead men tell no tales, incapacitated men fight no fights + if(attacker_controller.incapacitated()) + return FALSE + //if the attacker_controller isn't next to the attacking toy (and doesn't have telekinesis), the battle ends + if(!in_range(attacker, attacker_controller) && !(attacker_controller.dna.check_mutation(TK))) + attacker_controller.visible_message(" [attacker_controller.name] seperates from [attacker], ending the battle.", \ + " You separate from [attacker], ending the battle. ") + return FALSE + + //if it's PVP and the opponent is not next to the defending(src) toy (and doesn't have telekinesis), the battle ends + if(opponent) + if(opponent.incapacitated()) + return FALSE + if(!in_range(src, opponent) && !(opponent.dna.check_mutation(TK))) + opponent.visible_message(" [opponent.name] seperates from [src], ending the battle.", \ + " You separate from [src], ending the battle. ") + return FALSE + //if it's not PVP and the attacker_controller isn't next to the defending toy (and doesn't have telekinesis), the battle ends + else + if (!in_range(src, attacker_controller) && !(attacker_controller.dna.check_mutation(TK))) + attacker_controller.visible_message(" [attacker_controller.name] seperates from [src] and [attacker], ending the battle.", \ + " You separate [attacker] and [src], ending the battle. ") + return FALSE + + //if all that is good, then we can sleep peacefully + sleep(delay) + return TRUE + +//all credit to skasi for toy mech fun ideas +/obj/item/toy/prize/attack_self(mob/user) + if(timer < world.time) + to_chat(user, "You play with [src].") + timer = world.time + cooldown + if(!quiet) + playsound(user, 'sound/mecha/mechstep.ogg', 20, TRUE) + else + . = ..() + +/obj/item/toy/prize/attack_hand(mob/user) + . = ..() + if(.) + return + if(loc == user) + attack_self(user) + +/** + * If you attack a mech with a mech, initiate combat between them + */ +/obj/item/toy/prize/attackby(obj/item/user_toy, mob/living/user) + if(istype(user_toy, /obj/item/toy/prize)) + var/obj/item/toy/prize/P = user_toy + if(check_battle_start(user, P)) + mecha_brawl(P, user) + ..() + +/** + * Attack is called from the user's toy, aimed at target(another human), checking for target's toy. + */ +/obj/item/toy/prize/attack(mob/living/carbon/human/target, mob/living/carbon/human/user) + if(target == user) + to_chat(user, "Target another toy mech if you want to start a battle with yourself.") + return + else if(user.a_intent != INTENT_HARM) + if(wants_to_battle) //prevent spamming someone with offers + to_chat(user, "You already are offering battle to someone!") + return + if(!check_battle_start(user)) //if the user's mech isn't ready, don't bother checking + return + + for(var/obj/item/I in target.held_items) + if(istype(I, /obj/item/toy/prize)) //if you attack someone with a mech who's also holding a mech, offer to battle them + var/obj/item/toy/prize/P = I + if(!P.check_battle_start(target, null, user)) //check if the attacker mech is ready + break + + //slap them with the metaphorical white glove + if(P.wants_to_battle) //if the target mech wants to battle, initiate the battle from their POV + mecha_brawl(P, target, user) //P = defender's mech / SRC = attacker's mech / target = defender / user = attacker + P.wants_to_battle = FALSE + return + + //extend the offer of battle to the other mech + to_chat(user, "You offer battle to [target.name]!") + to_chat(target, "[user.name] wants to battle with [user.p_their()] [name]! Attack them with a toy mech to initiate combat.") + wants_to_battle = TRUE + addtimer(CALLBACK(src, .proc/withdraw_offer, user), 6 SECONDS) + return + + ..() + +/** + * Overrides attack_tk - Sorry, you have to be face to face to initiate a battle, it's good sportsmanship + */ +/obj/item/toy/prize/attack_tk(mob/user) + if(timer < world.time) + to_chat(user, "You telekinetically play with [src].") + timer = world.time + cooldown + if(!quiet) + playsound(user, 'sound/mecha/mechstep.ogg', 20, TRUE) + +/** + * Resets the request for battle. + * + * For use in a timer, this proc resets the wants_to_battle variable after a short period. + * Arguments: + * * user - the user wanting to do battle + */ +/obj/item/toy/prize/proc/withdraw_offer(mob/living/carbon/user) + if(wants_to_battle) + wants_to_battle = FALSE + to_chat(user, "You get the feeling they don't want to battle.") +/** + * Starts a battle, toy mech vs player. Player... doesn't win. + */ +/obj/item/toy/prize/suicide_act(mob/living/carbon/user) + if(in_combat) + to_chat(user, "[src] is in battle, let it finish first.") + return + + user.visible_message("[user] begins a fight [user.p_they()] can't win with [src]! It looks like [user.p_theyre()] trying to commit suicide!") + + in_combat = TRUE + sleep(1.5 SECONDS) + for(var/i in 1 to 4) + switch(i) + if(1, 3) + SpinAnimation(5, 0) + playsound(src, 'sound/mecha/mechstep.ogg', 30, TRUE) + user.adjustBruteLoss(25) + user.adjustStaminaLoss(50) + if(2) + user.SpinAnimation(5, 0) + playsound(user, 'sound/weapons/smash.ogg', 20, TRUE) + combat_health-- //we scratched it! + if(4) + say(special_attack_cry + "!!") + user.adjustStaminaLoss(25) + + if(!combat_sleep(1 SECONDS, null, user)) + say("PATHETIC.") + combat_health = max_combat_health + in_combat = FALSE + return SHAME + + sleep(0.5 SECONDS) + user.adjustBruteLoss(450) + + in_combat = FALSE + say("AN EASY WIN. MY POWER INCREASES.") // steal a soul, become swole + add_atom_colour(rgb(255, 115, 115), ADMIN_COLOUR_PRIORITY) + max_combat_health = round(max_combat_health*1.5 + 0.1) + combat_health = max_combat_health + wins++ + return BRUTELOSS + +/obj/item/toy/prize/examine() + . = ..() + . += "This toy's special attack is [special_attack_cry], [special_attack_type_message] " + if(in_combat) + . += "This toy has a maximum health of [max_combat_health]. Currently, it's [combat_health]." + . += "Its special move light is [special_attack_cooldown? "flashing red." : "green and is ready!"]" + else + . += "This toy has a maximum health of [max_combat_health]." + + if(wins || losses) + . += "This toy has [wins] wins, and [losses] losses." + +/** + * Override the say proc if they're mute + */ +/obj/item/toy/prize/say() + if(!quiet) + . = ..() + +/** + * The 'master' proc of the mech battle. Processes the entire battle's events and makes sure it start and finishes correctly. + * + * src is the defending toy, and the battle proc is called on it to begin the battle. + * After going through a few checks at the beginning to ensure the battle can start properly, the battle begins a loop that lasts + * until either toy has no more health. During this loop, it also ensures the mechs stay in combat range of each other. + * It will then randomly decide attacks for each toy, occasionally making one or the other use their special attack. + * When either mech has no more health, the loop ends, and it displays the victor and the loser while updating their stats and resetting them. + * Arguments: + * * attacker - the attacking toy, the toy in the attacker_controller's hands + * * attacker_controller - the user, the one who is holding the toys / controlling the fight + * * opponent - optional arg used in Mech PvP battles: the other person who is taking part in the fight (controls src) + */ +/obj/item/toy/prize/proc/mecha_brawl(obj/item/toy/prize/attacker, mob/living/carbon/attacker_controller, mob/living/carbon/opponent) + //A GOOD DAY FOR A SWELL BATTLE! + attacker_controller.visible_message(" [attacker_controller.name] collides [attacker] with [src]! Looks like they're preparing for a brawl! ", \ + " You collide [attacker] into [src], sparking a fierce battle! ", \ + " You hear hard plastic smacking into hard plastic.", COMBAT_MESSAGE_RANGE) + + /// Who's in control of the defender (src)? + var/mob/living/carbon/src_controller = (opponent)? opponent : attacker_controller + /// How long has the battle been going? + var/battle_length = 0 + + in_combat = TRUE + attacker.in_combat = TRUE + + //1.5 second cooldown * 20 = 30 second cooldown after a fight + timer = world.time + cooldown*20 + attacker.timer = world.time + attacker.cooldown*20 + + sleep(1 SECONDS) + //--THE BATTLE BEGINS-- + while(combat_health > 0 && attacker.combat_health > 0 && battle_length < MAX_BATTLE_LENGTH) + if(!combat_sleep(0.5 SECONDS, attacker, attacker_controller, opponent)) //combat_sleep checks everything we need to have checked for combat to continue + break + + //before we do anything - deal with charged attacks + if(special_attack_charged) + src_controller.visible_message(" [src] unleashes its special attack!! ", \ + " You unleash [src]'s special attack! ") + special_attack_move(attacker) + else if(attacker.special_attack_charged) + + attacker_controller.visible_message(" [attacker] unleashes its special attack!! ", \ + " You unleash [attacker]'s special attack! ") + attacker.special_attack_move(src) + else + //process the cooldowns + if(special_attack_cooldown > 0) + special_attack_cooldown-- + if(attacker.special_attack_cooldown > 0) + attacker.special_attack_cooldown-- + + //combat commences + switch(rand(1,8)) + if(1 to 3) //attacker wins + if(attacker.special_attack_cooldown == 0 && attacker.combat_health <= round(attacker.max_combat_health/3)) //if health is less than 1/3 and special off CD, use it + attacker.special_attack_charged = TRUE + attacker_controller.visible_message(" [attacker] begins charging its special attack!! ", \ + " You begin charging [attacker]'s special attack! ") + else //just attack + attacker.SpinAnimation(5, 0) + playsound(attacker, 'sound/mecha/mechstep.ogg', 30, TRUE) + combat_health-- + attacker_controller.visible_message(" [attacker] devastates [src]! ", \ + " You ram [attacker] into [src]! ", \ + " You hear hard plastic smacking hard plastic.", COMBAT_MESSAGE_RANGE) + if(prob(5)) + combat_health-- + playsound(src, 'sound/effects/meteorimpact.ogg', 20, TRUE) + attacker_controller.visible_message(" ...and lands a CRIPPLING BLOW! ", \ + " ...and you land a CRIPPLING blow on [src]! ", null, COMBAT_MESSAGE_RANGE) + + if(4) //both lose + attacker.SpinAnimation(5, 0) + SpinAnimation(5, 0) + combat_health-- + attacker.combat_health-- + do_sparks(2, FALSE, src) + do_sparks(2, FALSE, attacker) + if(prob(50)) + attacker_controller.visible_message(" [attacker] and [src] clash dramatically, causing sparks to fly! ", \ + " [attacker] and [src] clash dramatically, causing sparks to fly! ", \ + " You hear hard plastic rubbing against hard plastic.", COMBAT_MESSAGE_RANGE) + else + src_controller.visible_message(" [src] and [attacker] clash dramatically, causing sparks to fly! ", \ + " [src] and [attacker] clash dramatically, causing sparks to fly! ", \ + " You hear hard plastic rubbing against hard plastic.", COMBAT_MESSAGE_RANGE) + if(5) //both win + playsound(attacker, 'sound/weapons/parry.ogg', 20, TRUE) + if(prob(50)) + attacker_controller.visible_message(" [src]'s attack deflects off of [attacker]. ", \ + " [src]'s attack deflects off of [attacker]. ", \ + " You hear hard plastic bouncing off hard plastic.", COMBAT_MESSAGE_RANGE) + else + src_controller.visible_message(" [attacker]'s attack deflects off of [src]. ", \ + " [attacker]'s attack deflects off of [src]. ", \ + " You hear hard plastic bouncing off hard plastic.", COMBAT_MESSAGE_RANGE) + + if(6 to 8) //defender wins + if(special_attack_cooldown == 0 && combat_health <= round(max_combat_health/3)) //if health is less than 1/3 and special off CD, use it + special_attack_charged = TRUE + src_controller.visible_message(" [src] begins charging its special attack!! ", \ + " You begin charging [src]'s special attack! ") + else //just attack + SpinAnimation(5, 0) + playsound(src, 'sound/mecha/mechstep.ogg', 30, TRUE) + attacker.combat_health-- + src_controller.visible_message(" [src] smashes [attacker]! ", \ + " You smash [src] into [attacker]! ", \ + " You hear hard plastic smashing hard plastic.", COMBAT_MESSAGE_RANGE) + if(prob(5)) + attacker.combat_health-- + playsound(attacker, 'sound/effects/meteorimpact.ogg', 20, TRUE) + src_controller.visible_message(" ...and lands a CRIPPLING BLOW! ", \ + " ...and you land a CRIPPLING blow on [attacker]! ", null, COMBAT_MESSAGE_RANGE) + else + attacker_controller.visible_message(" [src] and [attacker] stand around awkwardly.", \ + " You don't know what to do next.") + + battle_length++ + sleep(0.5 SECONDS) + + /// Lines chosen for the winning mech + var/list/winlines = list("YOU'RE NOTHING BUT SCRAP!", "I'LL YIELD TO NONE!", "GLORY IS MINE!", "AN EASY FIGHT.", "YOU SHOULD HAVE NEVER FACED ME.", "ROCKED AND SOCKED.") + + if(attacker.combat_health <= 0 && combat_health <= 0) //both lose + playsound(src, 'sound/machines/warning-buzzer.ogg', 20, TRUE) + attacker_controller.visible_message(" MUTUALLY ASSURED DESTRUCTION!! [src] and [attacker] both end up destroyed!", \ + " Both [src] and [attacker] are destroyed!") + else if(attacker.combat_health <= 0) //src wins + wins++ + attacker.losses++ + playsound(attacker, 'sound/effects/light_flicker.ogg', 20, TRUE) + attacker_controller.visible_message(" [attacker] falls apart!", \ + " [attacker] falls apart!", null, COMBAT_MESSAGE_RANGE) + say("[pick(winlines)]") + src_controller.visible_message(" [src] destroys [attacker] and walks away victorious!", \ + " You raise up [src] victoriously over [attacker]!") + else if (combat_health <= 0) //attacker wins + attacker.wins++ + losses++ + playsound(src, 'sound/effects/light_flicker.ogg', 20, TRUE) + src_controller.visible_message(" [src] collapses!", \ + " [src] collapses!", null, COMBAT_MESSAGE_RANGE) + attacker.say("[pick(winlines)]") + attacker_controller.visible_message(" [attacker] demolishes [src] and walks away victorious!", \ + " You raise up [attacker] proudly over [src]!") + else //both win? + say("NEXT TIME.") + //don't want to make this a one sided conversation + quiet? attacker.say("I WENT EASY ON YOU.") : attacker.say("OF COURSE.") + + in_combat = FALSE + attacker.in_combat = FALSE + + combat_health = max_combat_health + attacker.combat_health = attacker.max_combat_health + + return + +/** + * This proc checks if a battle can be initiated between src and attacker. + * + * Both SRC and attacker (if attacker is included) timers are checked if they're on cooldown, and + * both SRC and attacker (if attacker is included) are checked if they are in combat already. + * If any of the above are true, the proc returns FALSE and sends a message to user (and target, if included) otherwise, it returns TRUE + * Arguments: + * * user: the user who is initiating the battle + * * attacker: optional arg for checking two mechs at once + * * target: optional arg used in Mech PvP battles (if used, attacker is target's toy) + */ +/obj/item/toy/prize/proc/check_battle_start(mob/living/carbon/user, obj/item/toy/prize/attacker, mob/living/carbon/target) + if(attacker && attacker.in_combat) + to_chat(user, "[target?target.p_their() : "Your" ] [attacker.name] is in combat.") + target?.to_chat(target, "Your [attacker.name] is in combat.") + return FALSE + if(in_combat) + to_chat(user, "Your [name] is in combat.") + target?.to_chat(target, "[target.p_their()] [name] is in combat.") + return FALSE + if(attacker && attacker.timer > world.time) + to_chat(user, "[target?target.p_their() : "Your" ] [attacker.name] isn't ready for battle.") + target?.to_chat(target, "Your [attacker.name] isn't ready for battle.") + return FALSE + if(timer > world.time) + to_chat(user, "Your [name] isn't ready for battle.") + target?.to_chat(target, "[target.p_their()] [name] isn't ready for battle.") + return FALSE + + return TRUE + +/** + * Processes any special attack moves that happen in the battle (called in the mechaBattle proc). + * + * Makes the toy shout their special attack cry and updates its cooldown. Then, does the special attack. + * Arguments: + * * victim - the toy being hit by the special move + */ +/obj/item/toy/prize/proc/special_attack_move(obj/item/toy/prize/victim) + say(special_attack_cry + "!!") + + special_attack_charged = FALSE + special_attack_cooldown = 3 + + switch(special_attack_type) + if(SPECIAL_ATTACK_DAMAGE) //+2 damage + victim.combat_health-=2 + playsound(src, 'sound/weapons/marauder.ogg', 20, TRUE) + if(SPECIAL_ATTACK_HEAL) //+2 healing + combat_health+=2 + playsound(src, 'sound/mecha/mech_shield_raise.ogg', 20, TRUE) + if(SPECIAL_ATTACK_UTILITY) //+1 heal, +1 damage + victim.combat_health-- + combat_health++ + playsound(src, 'sound/mecha/mechmove01.ogg', 30, TRUE) + if(SPECIAL_ATTACK_OTHER) //other + super_special_attack(victim) + else + say("I FORGOT MY SPECIAL ATTACK...") + +/** + * Base proc for 'other' special attack moves. + * + * This one is only for inheritance, each mech with an 'other' type move has their procs below. + * Arguments: + * * victim - the toy being hit by the super special move (doesn't necessarily need to be used) + */ +/obj/item/toy/prize/proc/super_special_attack(obj/item/toy/prize/victim) + visible_message(" [src] does a cool flip.") + +/obj/item/toy/prize/ripley + name = "toy Ripley" + desc = "1/13" + max_combat_health = 3 + special_attack_type = SPECIAL_ATTACK_DAMAGE + special_attack_cry = "OMEGA DRILL" + +/obj/item/toy/prize/fireripley + name = "toy Firefighting Ripley" + desc = "2/13" + icon_state = "fireripleytoy" + max_combat_health = 4 + special_attack_type = SPECIAL_ATTACK_UTILITY + special_attack_cry = "FIRE SHIELD" + +/obj/item/toy/prize/deathripley + name = "toy Deathsquad Ripley" + desc = "3/13" + icon_state = "deathripleytoy" + max_combat_health = 5 + special_attack_type = SPECIAL_ATTACK_OTHER + special_attack_type_message = "instantly destroys the opposing mech if its health is less than this mech's health." + special_attack_cry = "KILLER CLAMP" + +/obj/item/toy/prize/deathripley/super_special_attack(obj/item/toy/prize/victim) + playsound(src, 'sound/weapons/sonic_jackhammer.ogg', 20, TRUE) + if(victim.combat_health < combat_health) //Instantly kills the other mech if it's health is below our's. + say("EXECUTE!!") + victim.combat_health = 0 + else //Otherwise, just deal one damage. + victim.combat_health-- + +/obj/item/toy/prize/gygax + name = "toy Gygax" + desc = "4/13" + icon_state = "gygaxtoy" + max_combat_health = 5 + special_attack_type = SPECIAL_ATTACK_UTILITY + special_attack_cry = "SUPER SERVOS" + +/obj/item/toy/prize/durand + name = "toy Durand" + desc = "5/13" + icon_state = "durandtoy" + max_combat_health = 6 + special_attack_type = SPECIAL_ATTACK_HEAL + special_attack_cry = "SHIELD OF PROTECTION" + +/obj/item/toy/prize/honk + name = "toy H.O.N.K." + desc = "6/13" + icon_state = "honktoy" + max_combat_health = 4 + special_attack_type = SPECIAL_ATTACK_OTHER + special_attack_type_message = "puts the opposing mech's special move on cooldown and heals this mech." + special_attack_cry = "MEGA HORN" + +/obj/item/toy/prize/honk/super_special_attack(obj/item/toy/prize/victim) + playsound(src, 'sound/machines/honkbot_evil_laugh.ogg', 20, TRUE) + victim.special_attack_cooldown = 3 //Adds cooldown to the other mech and gives a minor self heal + combat_health++ + +/obj/item/toy/prize/marauder + name = "toy Marauder" + desc = "7/13" + icon_state = "maraudertoy" + max_combat_health = 7 + special_attack_type = SPECIAL_ATTACK_DAMAGE + special_attack_cry = "BEAM BLAST" + +/obj/item/toy/prize/seraph + name = "toy Seraph" + desc = "8/13" + icon_state = "seraphtoy" + max_combat_health = 8 + special_attack_type = SPECIAL_ATTACK_DAMAGE + special_attack_cry = "ROCKET BARRAGE" + +/obj/item/toy/prize/mauler + name = "toy Mauler" + desc = "9/13" + icon_state = "maulertoy" + max_combat_health = 8 + special_attack_type = SPECIAL_ATTACK_DAMAGE + special_attack_cry = "BULLET STORM" + +/obj/item/toy/prize/odysseus + name = "toy Odysseus" + desc = "10/13" + icon_state = "odysseustoy" + max_combat_health = 4 + special_attack_type = SPECIAL_ATTACK_HEAL + special_attack_cry = "MECHA BEAM" + +/obj/item/toy/prize/phazon + name = "toy Phazon" + desc = "11/13" + icon_state = "phazontoy" + max_combat_health = 6 + special_attack_type = SPECIAL_ATTACK_UTILITY + special_attack_cry = "NO-CLIP" + +/obj/item/toy/prize/reticence + name = "toy Reticence" + desc = "12/13" + icon_state = "reticencetoy" + quiet = TRUE + max_combat_health = 4 + special_attack_type = SPECIAL_ATTACK_OTHER + special_attack_type_message = "has a lower cooldown than normal special moves, increases the opponent's cooldown, and deals damage." + special_attack_cry = "*wave" + +/obj/item/toy/prize/reticence/super_special_attack(obj/item/toy/prize/victim) + special_attack_cooldown-- //Has a lower cooldown... + victim.special_attack_cooldown++ //and increases the opponent's cooldown by 1... + victim.combat_health-- //and some free damage. + +/obj/item/toy/prize/clarke + name = "toy Clarke" + desc = "13/13" + icon_state = "clarketoy" + max_combat_health = 4 + special_attack_type = SPECIAL_ATTACK_UTILITY + special_attack_cry = "ROLL OUT" + +#undef SPECIAL_ATTACK_HEAL +#undef SPECIAL_ATTACK_DAMAGE +#undef SPECIAL_ATTACK_UTILITY +#undef SPECIAL_ATTACK_OTHER +#undef MAX_BATTLE_LENGTH diff --git a/code/game/objects/items/toys.dm b/code/game/objects/items/toys.dm index 7021b5b2860..dfb14d30a02 100644 --- a/code/game/objects/items/toys.dm +++ b/code/game/objects/items/toys.dm @@ -7,7 +7,6 @@ * Toy swords * Crayons * Snap pops - * Mech prizes * AI core prizes * Toy codex gigas * Skeleton toys @@ -26,15 +25,6 @@ * Broken Radio */ -/// Mech battle special attack types. -#define SPECIAL_ATTACK_HEAL 1 -#define SPECIAL_ATTACK_DAMAGE 2 -#define SPECIAL_ATTACK_UTILITY 3 -#define SPECIAL_ATTACK_OTHER 4 - -/// Max length of a mech battle -#define MAX_BATTLE_LENGTH 50 - /obj/item/toy throwforce = 0 throw_speed = 3 @@ -526,622 +516,6 @@ new /obj/item/toy/snappop/phoenix(get_turf(src)) qdel(src) - -/** - * Mech prizes + MECHA COMBAT!! - */ -/obj/item/toy/prize - icon = 'icons/obj/toy.dmi' - icon_state = "ripleytoy" - verb_say = "beeps" - verb_ask = "beeps" - verb_exclaim = "beeps" - verb_yell = "beeps" - w_class = WEIGHT_CLASS_SMALL - /// Timer when it'll be off cooldown - var/timer = 0 - /// Cooldown between play sessions - 20x longer after a battle - var/cooldown = 1.5 SECONDS - /// If it makes noise when played with - var/quiet = FALSE - /// TRUE = Offering battle to someone || FALSE = Not offering battle - var/wants_to_battle = FALSE - /// TRUE = in combat currently || FALSE = Not in combat - var/in_combat = FALSE - /// The mech's health in battle - var/combat_health = 0 - /// The mech's max combat health - var/max_combat_health = 0 - /// TRUE = the special attack is charged || FALSE = not charged - var/special_attack_charged = FALSE - /// What type of special attack they use - SPECIAL_ATTACK_DAMAGE, SPECIAL_ATTACK_HEAL, SPECIAL_ATTACK_UTILITY, SPECIAL_ATTACK_OTHER - var/special_attack_type = 0 - /// What message their special move gets on examining - var/special_attack_type_message = "" - /// The battlecry when using the special attack - var/special_attack_cry = "*flip" - /// Current cooldown of their special attack - var/special_attack_cooldown = 0 - /// This mech's win count in combat - var/wins = 0 - /// ...And their loss count in combat - var/losses = 0 - -/obj/item/toy/prize/Initialize() - . = ..() - desc = "Mini-Mecha action figure! Collect them all! Attack your friends or another mech with one to initiate epic mech combat! [desc]." - combat_health = max_combat_health - switch(special_attack_type) - if(SPECIAL_ATTACK_DAMAGE) - special_attack_type_message = "an aggressive move, which deals bonus damage." - if(SPECIAL_ATTACK_HEAL) - special_attack_type_message = "a defensive move, which grants bonus healing." - if(SPECIAL_ATTACK_UTILITY) - special_attack_type_message = "a utility move, which heals the user and damages the opponent." - if(SPECIAL_ATTACK_OTHER) - special_attack_type_message = "a special move, which [special_attack_type_message]" - else - special_attack_type_message = "a mystery move, even I don't know." - -/** - * this proc combines "sleep" while also checking for if the battle should continue - * - * this goes through some of the checks - the toys need to be next to each other to fight! - * if it's player vs themself: They need to be able to "control" both mechs (either must be adjacent or using TK) - * if it's player vs player: Both players need to be able to "control" their mechs (either must be adjacent or using TK) - * if it's player vs mech (suicide): the mech needs to be in range of the player - * if all the checks are TRUE, it does the sleeps, and returns TRUE. Otherwise, it returns FALSE. - * Arguments: - * * delay - the amount of time the sleep at the end of the check will sleep for - * * attacker - the attacking toy in the battle. - * * attacker_controller - the controller of the attacking toy. there should ALWAYS be an attacker_controller - * * opponent - (optional) the defender controller in the battle, for PvP - */ -/obj/item/toy/prize/proc/combat_sleep(var/delay, obj/item/toy/prize/attacker, mob/living/carbon/attacker_controller, mob/living/carbon/opponent) - if(!attacker_controller) - return FALSE - - if(!attacker) //if there's no attacker, then attacker_controller IS the attacker - if(!in_range(src, attacker_controller)) - attacker_controller.visible_message("[attacker_controller] is running from [src]! The coward!") - return FALSE - else // if there's an attacker, we can procede as normal - if(!in_range(src, attacker)) //and the two toys aren't next to each other, the battle ends - attacker_controller.visible_message(" [attacker] and [src] separate, ending the battle. ", \ - " [attacker] and [src] separate, ending the battle. ") - return FALSE - - //dead men tell no tales, incapacitated men fight no fights - if(attacker_controller.incapacitated()) - return FALSE - //if the attacker_controller isn't next to the attacking toy (and doesn't have telekinesis), the battle ends - if(!in_range(attacker, attacker_controller) && !(attacker_controller.dna.check_mutation(TK))) - attacker_controller.visible_message(" [attacker_controller.name] seperates from [attacker], ending the battle.", \ - " You separate from [attacker], ending the battle. ") - return FALSE - - //if it's PVP and the opponent is not next to the defending(src) toy (and doesn't have telekinesis), the battle ends - if(opponent) - if(opponent.incapacitated()) - return FALSE - if(!in_range(src, opponent) && !(opponent.dna.check_mutation(TK))) - opponent.visible_message(" [opponent.name] seperates from [src], ending the battle.", \ - " You separate from [src], ending the battle. ") - return FALSE - //if it's not PVP and the attacker_controller isn't next to the defending toy (and doesn't have telekinesis), the battle ends - else - if (!in_range(src, attacker_controller) && !(attacker_controller.dna.check_mutation(TK))) - attacker_controller.visible_message(" [attacker_controller.name] seperates from [src] and [attacker], ending the battle.", \ - " You separate [attacker] and [src], ending the battle. ") - return FALSE - - //if all that is good, then we can sleep peacefully - sleep(delay) - return TRUE - -//all credit to skasi for toy mech fun ideas -/obj/item/toy/prize/attack_self(mob/user) - if(timer < world.time) - to_chat(user, "You play with [src].") - timer = world.time + cooldown - if(!quiet) - playsound(user, 'sound/mecha/mechstep.ogg', 20, TRUE) - else - . = ..() - -/obj/item/toy/prize/attack_hand(mob/user) - . = ..() - if(.) - return - if(loc == user) - attack_self(user) - -/** - * If you attack a mech with a mech, initiate combat between them - */ -/obj/item/toy/prize/attackby(obj/item/user_toy, mob/living/user) - if(istype(user_toy, /obj/item/toy/prize)) - var/obj/item/toy/prize/P = user_toy - if(check_battle_start(user, P)) - mecha_brawl(P, user) - ..() - -/** - * Attack is called from the user's toy, aimed at target(another human), checking for target's toy. - */ -/obj/item/toy/prize/attack(mob/living/carbon/human/target, mob/living/carbon/human/user) - if(target == user) - to_chat(user, "Target another toy mech if you want to start a battle with yourself.") - return - else if(user.a_intent != INTENT_HARM) - if(wants_to_battle) //prevent spamming someone with offers - to_chat(user, "You already are offering battle to someone!") - return - if(!check_battle_start(user)) //if the user's mech isn't ready, don't bother checking - return - - for(var/obj/item/I in target.held_items) - if(istype(I, /obj/item/toy/prize)) //if you attack someone with a mech who's also holding a mech, offer to battle them - var/obj/item/toy/prize/P = I - if(!P.check_battle_start(target, null, user)) //check if the attacker mech is ready - break - - //slap them with the metaphorical white glove - if(P.wants_to_battle) //if the target mech wants to battle, initiate the battle from their POV - mecha_brawl(P, target, user) //P = defender's mech / SRC = attacker's mech / target = defender / user = attacker - P.wants_to_battle = FALSE - return - - //extend the offer of battle to the other mech - to_chat(user, "You offer battle to [target.name]!") - to_chat(target, "[user.name] wants to battle with [user.p_their()] [name]! Attack them with a toy mech to initiate combat.") - wants_to_battle = TRUE - addtimer(CALLBACK(src, .proc/withdraw_offer, user), 6 SECONDS) - return - - ..() - -/** - * Overrides attack_tk - Sorry, you have to be face to face to initiate a battle, it's good sportsmanship - */ -/obj/item/toy/prize/attack_tk(mob/user) - if(timer < world.time) - to_chat(user, "You telekinetically play with [src].") - timer = world.time + cooldown - if(!quiet) - playsound(user, 'sound/mecha/mechstep.ogg', 20, TRUE) - -/** - * Resets the request for battle. - * - * For use in a timer, this proc resets the wants_to_battle variable after a short period. - * Arguments: - * * user - the user wanting to do battle - */ -/obj/item/toy/prize/proc/withdraw_offer(mob/living/carbon/user) - if(wants_to_battle) - wants_to_battle = FALSE - to_chat(user, "You get the feeling they don't want to battle.") -/** - * Starts a battle, toy mech vs player. Player... doesn't win. - */ -/obj/item/toy/prize/suicide_act(mob/living/carbon/user) - if(in_combat) - to_chat(user, "[src] is in battle, let it finish first.") - return - - user.visible_message("[user] begins a fight [user.p_they()] can't win with [src]! It looks like [user.p_theyre()] trying to commit suicide!") - - in_combat = TRUE - sleep(1.5 SECONDS) - for(var/i in 1 to 4) - switch(i) - if(1, 3) - SpinAnimation(5, 0) - playsound(src, 'sound/mecha/mechstep.ogg', 30, TRUE) - user.adjustBruteLoss(25) - user.adjustStaminaLoss(50) - if(2) - user.SpinAnimation(5, 0) - playsound(user, 'sound/weapons/smash.ogg', 20, TRUE) - combat_health-- //we scratched it! - if(4) - say(special_attack_cry + "!!") - user.adjustStaminaLoss(25) - - if(!combat_sleep(1 SECONDS, null, user)) - say("PATHETIC.") - combat_health = max_combat_health - in_combat = FALSE - return SHAME - - sleep(0.5 SECONDS) - user.adjustBruteLoss(450) - - in_combat = FALSE - say("AN EASY WIN. MY POWER INCREASES.") // steal a soul, become swole - add_atom_colour(rgb(255, 115, 115), ADMIN_COLOUR_PRIORITY) - max_combat_health = round(max_combat_health*1.5 + 0.1) - combat_health = max_combat_health - wins++ - return BRUTELOSS - -/obj/item/toy/prize/examine() - . = ..() - . += "This toy's special attack is [special_attack_cry], [special_attack_type_message] " - if(in_combat) - . += "This toy has a maximum health of [max_combat_health]. Currently, it's [combat_health]." - . += "Its special move light is [special_attack_cooldown? "flashing red." : "green and is ready!"]" - else - . += "This toy has a maximum health of [max_combat_health]." - - if(wins || losses) - . += "This toy has [wins] wins, and [losses] losses." - -/** - * Override the say proc if they're mute - */ -/obj/item/toy/prize/say() - if(!quiet) - . = ..() - -/** - * The 'master' proc of the mech battle. Processes the entire battle's events and makes sure it start and finishes correctly. - * - * src is the defending toy, and the battle proc is called on it to begin the battle. - * After going through a few checks at the beginning to ensure the battle can start properly, the battle begins a loop that lasts - * until either toy has no more health. During this loop, it also ensures the mechs stay in combat range of each other. - * It will then randomly decide attacks for each toy, occasionally making one or the other use their special attack. - * When either mech has no more health, the loop ends, and it displays the victor and the loser while updating their stats and resetting them. - * Arguments: - * * attacker - the attacking toy, the toy in the attacker_controller's hands - * * attacker_controller - the user, the one who is holding the toys / controlling the fight - * * opponent - optional arg used in Mech PvP battles: the other person who is taking part in the fight (controls src) - */ -/obj/item/toy/prize/proc/mecha_brawl(obj/item/toy/prize/attacker, mob/living/carbon/attacker_controller, mob/living/carbon/opponent) - //A GOOD DAY FOR A SWELL BATTLE! - attacker_controller.visible_message(" [attacker_controller.name] collides [attacker] with [src]! Looks like they're preparing for a brawl! ", \ - " You collide [attacker] into [src], sparking a fierce battle! ", \ - " You hear hard plastic smacking into hard plastic.", COMBAT_MESSAGE_RANGE) - - /// Who's in control of the defender (src)? - var/mob/living/carbon/src_controller = (opponent)? opponent : attacker_controller - /// How long has the battle been going? - var/battle_length = 0 - - in_combat = TRUE - attacker.in_combat = TRUE - - //1.5 second cooldown * 20 = 30 second cooldown after a fight - timer = world.time + cooldown*20 - attacker.timer = world.time + attacker.cooldown*20 - - sleep(1 SECONDS) - //--THE BATTLE BEGINS-- - while(combat_health > 0 && attacker.combat_health > 0 && battle_length < MAX_BATTLE_LENGTH) - if(!combat_sleep(0.5 SECONDS, attacker, attacker_controller, opponent)) //combat_sleep checks everything we need to have checked for combat to continue - break - - //before we do anything - deal with charged attacks - if(special_attack_charged) - src_controller.visible_message(" [src] unleashes its special attack!! ", \ - " You unleash [src]'s special attack! ") - special_attack_move(attacker) - else if(attacker.special_attack_charged) - - attacker_controller.visible_message(" [attacker] unleashes its special attack!! ", \ - " You unleash [attacker]'s special attack! ") - attacker.special_attack_move(src) - else - //process the cooldowns - if(special_attack_cooldown > 0) - special_attack_cooldown-- - if(attacker.special_attack_cooldown > 0) - attacker.special_attack_cooldown-- - - //combat commences - switch(rand(1,8)) - if(1 to 3) //attacker wins - if(attacker.special_attack_cooldown == 0 && attacker.combat_health <= round(attacker.max_combat_health/3)) //if health is less than 1/3 and special off CD, use it - attacker.special_attack_charged = TRUE - attacker_controller.visible_message(" [attacker] begins charging its special attack!! ", \ - " You begin charging [attacker]'s special attack! ") - else //just attack - attacker.SpinAnimation(5, 0) - playsound(attacker, 'sound/mecha/mechstep.ogg', 30, TRUE) - combat_health-- - attacker_controller.visible_message(" [attacker] devastates [src]! ", \ - " You ram [attacker] into [src]! ", \ - " You hear hard plastic smacking hard plastic.", COMBAT_MESSAGE_RANGE) - if(prob(5)) - combat_health-- - playsound(src, 'sound/effects/meteorimpact.ogg', 20, TRUE) - attacker_controller.visible_message(" ...and lands a CRIPPLING BLOW! ", \ - " ...and you land a CRIPPLING blow on [src]! ", null, COMBAT_MESSAGE_RANGE) - - if(4) //both lose - attacker.SpinAnimation(5, 0) - SpinAnimation(5, 0) - combat_health-- - attacker.combat_health-- - do_sparks(2, FALSE, src) - do_sparks(2, FALSE, attacker) - if(prob(50)) - attacker_controller.visible_message(" [attacker] and [src] clash dramatically, causing sparks to fly! ", \ - " [attacker] and [src] clash dramatically, causing sparks to fly! ", \ - " You hear hard plastic rubbing against hard plastic.", COMBAT_MESSAGE_RANGE) - else - src_controller.visible_message(" [src] and [attacker] clash dramatically, causing sparks to fly! ", \ - " [src] and [attacker] clash dramatically, causing sparks to fly! ", \ - " You hear hard plastic rubbing against hard plastic.", COMBAT_MESSAGE_RANGE) - if(5) //both win - playsound(attacker, 'sound/weapons/parry.ogg', 20, TRUE) - if(prob(50)) - attacker_controller.visible_message(" [src]'s attack deflects off of [attacker]. ", \ - " [src]'s attack deflects off of [attacker]. ", \ - " You hear hard plastic bouncing off hard plastic.", COMBAT_MESSAGE_RANGE) - else - src_controller.visible_message(" [attacker]'s attack deflects off of [src]. ", \ - " [attacker]'s attack deflects off of [src]. ", \ - " You hear hard plastic bouncing off hard plastic.", COMBAT_MESSAGE_RANGE) - - if(6 to 8) //defender wins - if(special_attack_cooldown == 0 && combat_health <= round(max_combat_health/3)) //if health is less than 1/3 and special off CD, use it - special_attack_charged = TRUE - src_controller.visible_message(" [src] begins charging its special attack!! ", \ - " You begin charging [src]'s special attack! ") - else //just attack - SpinAnimation(5, 0) - playsound(src, 'sound/mecha/mechstep.ogg', 30, TRUE) - attacker.combat_health-- - src_controller.visible_message(" [src] smashes [attacker]! ", \ - " You smash [src] into [attacker]! ", \ - " You hear hard plastic smashing hard plastic.", COMBAT_MESSAGE_RANGE) - if(prob(5)) - attacker.combat_health-- - playsound(attacker, 'sound/effects/meteorimpact.ogg', 20, TRUE) - src_controller.visible_message(" ...and lands a CRIPPLING BLOW! ", \ - " ...and you land a CRIPPLING blow on [attacker]! ", null, COMBAT_MESSAGE_RANGE) - else - attacker_controller.visible_message(" [src] and [attacker] stand around awkwardly.", \ - " You don't know what to do next.") - - battle_length++ - sleep(0.5 SECONDS) - - /// Lines chosen for the winning mech - var/list/winlines = list("YOU'RE NOTHING BUT SCRAP!", "I'LL YIELD TO NONE!", "GLORY IS MINE!", "AN EASY FIGHT.", "YOU SHOULD HAVE NEVER FACED ME.", "ROCKED AND SOCKED.") - - if(attacker.combat_health <= 0 && combat_health <= 0) //both lose - playsound(src, 'sound/machines/warning-buzzer.ogg', 20, TRUE) - attacker_controller.visible_message(" MUTUALLY ASSURED DESTRUCTION!! [src] and [attacker] both end up destroyed!", \ - " Both [src] and [attacker] are destroyed!") - else if(attacker.combat_health <= 0) //src wins - wins++ - attacker.losses++ - playsound(attacker, 'sound/effects/light_flicker.ogg', 20, TRUE) - attacker_controller.visible_message(" [attacker] falls apart!", \ - " [attacker] falls apart!", null, COMBAT_MESSAGE_RANGE) - say("[pick(winlines)]") - src_controller.visible_message(" [src] destroys [attacker] and walks away victorious!", \ - " You raise up [src] victoriously over [attacker]!") - else if (combat_health <= 0) //attacker wins - attacker.wins++ - losses++ - playsound(src, 'sound/effects/light_flicker.ogg', 20, TRUE) - src_controller.visible_message(" [src] collapses!", \ - " [src] collapses!", null, COMBAT_MESSAGE_RANGE) - attacker.say("[pick(winlines)]") - attacker_controller.visible_message(" [attacker] demolishes [src] and walks away victorious!", \ - " You raise up [attacker] proudly over [src]!") - else //both win? - say("NEXT TIME.") - //don't want to make this a one sided conversation - quiet? attacker.say("I WENT EASY ON YOU.") : attacker.say("OF COURSE.") - - in_combat = FALSE - attacker.in_combat = FALSE - - combat_health = max_combat_health - attacker.combat_health = attacker.max_combat_health - - return - -/** - * This proc checks if a battle can be initiated between src and attacker. - * - * Both SRC and attacker (if attacker is included) timers are checked if they're on cooldown, and - * both SRC and attacker (if attacker is included) are checked if they are in combat already. - * If any of the above are true, the proc returns FALSE and sends a message to user (and target, if included) otherwise, it returns TRUE - * Arguments: - * * user: the user who is initiating the battle - * * attacker: optional arg for checking two mechs at once - * * target: optional arg used in Mech PvP battles (if used, attacker is target's toy) - */ -/obj/item/toy/prize/proc/check_battle_start(mob/living/carbon/user, obj/item/toy/prize/attacker, mob/living/carbon/target) - if(attacker && attacker.in_combat) - to_chat(user, "[target?target.p_their() : "Your" ] [attacker.name] is in combat.") - target?.to_chat(target, "Your [attacker.name] is in combat.") - return FALSE - if(in_combat) - to_chat(user, "Your [name] is in combat.") - target?.to_chat(target, "[target.p_their()] [name] is in combat.") - return FALSE - if(attacker && attacker.timer > world.time) - to_chat(user, "[target?target.p_their() : "Your" ] [attacker.name] isn't ready for battle.") - target?.to_chat(target, "Your [attacker.name] isn't ready for battle.") - return FALSE - if(timer > world.time) - to_chat(user, "Your [name] isn't ready for battle.") - target?.to_chat(target, "[target.p_their()] [name] isn't ready for battle.") - return FALSE - - return TRUE - -/** - * Processes any special attack moves that happen in the battle (called in the mechaBattle proc). - * - * Makes the toy shout their special attack cry and updates its cooldown. Then, does the special attack. - * Arguments: - * * victim - the toy being hit by the special move - */ -/obj/item/toy/prize/proc/special_attack_move(obj/item/toy/prize/victim) - say(special_attack_cry + "!!") - - special_attack_charged = FALSE - special_attack_cooldown = 3 - - switch(special_attack_type) - if(SPECIAL_ATTACK_DAMAGE) //+2 damage - victim.combat_health-=2 - playsound(src, 'sound/weapons/marauder.ogg', 20, TRUE) - if(SPECIAL_ATTACK_HEAL) //+2 healing - combat_health+=2 - playsound(src, 'sound/mecha/mech_shield_raise.ogg', 20, TRUE) - if(SPECIAL_ATTACK_UTILITY) //+1 heal, +1 damage - victim.combat_health-- - combat_health++ - playsound(src, 'sound/mecha/mechmove01.ogg', 30, TRUE) - if(SPECIAL_ATTACK_OTHER) //other - super_special_attack(victim) - else - say("I FORGOT MY SPECIAL ATTACK...") - -/** - * Base proc for 'other' special attack moves. - * - * This one is only for inheritance, each mech with an 'other' type move has their procs below. - * Arguments: - * * victim - the toy being hit by the super special move (doesn't necessarily need to be used) - */ -/obj/item/toy/prize/proc/super_special_attack(obj/item/toy/prize/victim) - visible_message(" [src] does a cool flip.") - -/obj/item/toy/prize/ripley - name = "toy Ripley" - desc = "1/13" - max_combat_health = 3 - special_attack_type = SPECIAL_ATTACK_DAMAGE - special_attack_cry = "OMEGA DRILL" - -/obj/item/toy/prize/fireripley - name = "toy Firefighting Ripley" - desc = "2/13" - icon_state = "fireripleytoy" - max_combat_health = 4 - special_attack_type = SPECIAL_ATTACK_UTILITY - special_attack_cry = "FIRE SHIELD" - -/obj/item/toy/prize/deathripley - name = "toy Deathsquad Ripley" - desc = "3/13" - icon_state = "deathripleytoy" - max_combat_health = 5 - special_attack_type = SPECIAL_ATTACK_OTHER - special_attack_type_message = "instantly destroys the opposing mech if its health is less than this mech's health." - special_attack_cry = "KILLER CLAMP" - -/obj/item/toy/prize/deathripley/super_special_attack(obj/item/toy/prize/victim) - playsound(src, 'sound/weapons/sonic_jackhammer.ogg', 20, TRUE) - if(victim.combat_health < combat_health) //Instantly kills the other mech if it's health is below our's. - say("EXECUTE!!") - victim.combat_health = 0 - else //Otherwise, just deal one damage. - victim.combat_health-- - -/obj/item/toy/prize/gygax - name = "toy Gygax" - desc = "4/13" - icon_state = "gygaxtoy" - max_combat_health = 5 - special_attack_type = SPECIAL_ATTACK_UTILITY - special_attack_cry = "SUPER SERVOS" - -/obj/item/toy/prize/durand - name = "toy Durand" - desc = "5/13" - icon_state = "durandtoy" - max_combat_health = 6 - special_attack_type = SPECIAL_ATTACK_HEAL - special_attack_cry = "SHIELD OF PROTECTION" - -/obj/item/toy/prize/honk - name = "toy H.O.N.K." - desc = "6/13" - icon_state = "honktoy" - max_combat_health = 4 - special_attack_type = SPECIAL_ATTACK_OTHER - special_attack_type_message = "puts the opposing mech's special move on cooldown and heals this mech." - special_attack_cry = "MEGA HORN" - -/obj/item/toy/prize/honk/super_special_attack(obj/item/toy/prize/victim) - playsound(src, 'sound/machines/honkbot_evil_laugh.ogg', 20, TRUE) - victim.special_attack_cooldown = 3 //Adds cooldown to the other mech and gives a minor self heal - combat_health++ - -/obj/item/toy/prize/marauder - name = "toy Marauder" - desc = "7/13" - icon_state = "maraudertoy" - max_combat_health = 7 - special_attack_type = SPECIAL_ATTACK_DAMAGE - special_attack_cry = "BEAM BLAST" - -/obj/item/toy/prize/seraph - name = "toy Seraph" - desc = "8/13" - icon_state = "seraphtoy" - max_combat_health = 8 - special_attack_type = SPECIAL_ATTACK_DAMAGE - special_attack_cry = "ROCKET BARRAGE" - -/obj/item/toy/prize/mauler - name = "toy Mauler" - desc = "9/13" - icon_state = "maulertoy" - max_combat_health = 8 - special_attack_type = SPECIAL_ATTACK_DAMAGE - special_attack_cry = "BULLET STORM" - -/obj/item/toy/prize/odysseus - name = "toy Odysseus" - desc = "10/13" - icon_state = "odysseustoy" - max_combat_health = 4 - special_attack_type = SPECIAL_ATTACK_HEAL - special_attack_cry = "MECHA BEAM" - -/obj/item/toy/prize/phazon - name = "toy Phazon" - desc = "11/13" - icon_state = "phazontoy" - max_combat_health = 6 - special_attack_type = SPECIAL_ATTACK_UTILITY - special_attack_cry = "NO-CLIP" - -/obj/item/toy/prize/reticence - name = "toy Reticence" - desc = "12/13" - icon_state = "reticencetoy" - quiet = TRUE - max_combat_health = 4 - special_attack_type = SPECIAL_ATTACK_OTHER - special_attack_type_message = "has a lower cooldown than normal special moves, increases the opponent's cooldown, and deals damage." - special_attack_cry = "*wave" - -/obj/item/toy/prize/reticence/super_special_attack(obj/item/toy/prize/victim) - special_attack_cooldown-- //Has a lower cooldown... - victim.special_attack_cooldown++ //and increases the opponent's cooldown by 1... - victim.combat_health-- //and some free damage. - -/obj/item/toy/prize/clarke - name = "toy Clarke" - desc = "13/13" - icon_state = "clarketoy" - max_combat_health = 4 - special_attack_type = SPECIAL_ATTACK_UTILITY - special_attack_cry = "ROLL OUT" - /obj/item/toy/talking name = "talking action figure" desc = "A generic action figure modeled after nothing in particular." @@ -2099,9 +1473,3 @@ cooldown = (world.time + 10) sleep(5) playsound(src, 'sound/effects/blobattack.ogg', 50, FALSE) - -#undef SPECIAL_ATTACK_HEAL -#undef SPECIAL_ATTACK_DAMAGE -#undef SPECIAL_ATTACK_UTILITY -#undef SPECIAL_ATTACK_OTHER -#undef MAX_BATTLE_LENGTH diff --git a/tgstation.dme b/tgstation.dme index 7f32fde99a3..4bd72984ed8 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -1007,6 +1007,7 @@ #include "code\game\objects\items\teleprod.dm" #include "code\game\objects\items\theft_tools.dm" #include "code\game\objects\items\toys.dm" +#include "code\game\objects\items\toy_mechs.dm" #include "code\game\objects\items\trash.dm" #include "code\game\objects\items\vending_items.dm" #include "code\game\objects\items\wayfinding.dm" From 0567dd6efe5ef725777bfb83d8969c4abe2615fa Mon Sep 17 00:00:00 2001 From: MrMelbert Date: Wed, 24 Jun 2020 12:50:45 -0500 Subject: [PATCH 013/196] anime --- code/game/objects/items/toy_mechs.dm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/game/objects/items/toy_mechs.dm b/code/game/objects/items/toy_mechs.dm index 7ede120afb9..7fcdda390b1 100644 --- a/code/game/objects/items/toy_mechs.dm +++ b/code/game/objects/items/toy_mechs.dm @@ -503,7 +503,7 @@ desc = "1/13" max_combat_health = 3 special_attack_type = SPECIAL_ATTACK_DAMAGE - special_attack_cry = "OMEGA DRILL" + special_attack_cry = "GIGA DRILL BREAK" /obj/item/toy/prize/fireripley name = "toy Firefighting Ripley" From e99ac9bcae0d0163632ec4e961d9b0160c8ac3c4 Mon Sep 17 00:00:00 2001 From: MrMelbert Date: Thu, 25 Jun 2020 14:16:16 -0500 Subject: [PATCH 014/196] "balancing" --- code/game/objects/items/toy_mechs.dm | 38 +++++++++++++++------------- 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/code/game/objects/items/toy_mechs.dm b/code/game/objects/items/toy_mechs.dm index 7fcdda390b1..7f39b8f8869 100644 --- a/code/game/objects/items/toy_mechs.dm +++ b/code/game/objects/items/toy_mechs.dm @@ -21,8 +21,10 @@ w_class = WEIGHT_CLASS_SMALL /// Timer when it'll be off cooldown var/timer = 0 - /// Cooldown between play sessions - 20x longer after a battle + /// Cooldown between play sessions var/cooldown = 1.5 SECONDS + /// Cooldown multiplier after a battle (by default: battle cooldowns are 30 seconds) + var/cooldown_multiplier = 20 /// If it makes noise when played with var/quiet = FALSE /// TRUE = Offering battle to someone || FALSE = Not offering battle @@ -294,8 +296,8 @@ attacker.in_combat = TRUE //1.5 second cooldown * 20 = 30 second cooldown after a fight - timer = world.time + cooldown*20 - attacker.timer = world.time + attacker.cooldown*20 + timer = world.time + cooldown*cooldown_multiplier + attacker.timer = world.time + attacker.cooldown*attacker.cooldown_multiplier sleep(1 SECONDS) //--THE BATTLE BEGINS-- @@ -501,15 +503,15 @@ /obj/item/toy/prize/ripley name = "toy Ripley" desc = "1/13" - max_combat_health = 3 + max_combat_health = 4 //200 integrity special_attack_type = SPECIAL_ATTACK_DAMAGE special_attack_cry = "GIGA DRILL BREAK" -/obj/item/toy/prize/fireripley +/obj/item/toy/prize/fireripley //rip name = "toy Firefighting Ripley" desc = "2/13" icon_state = "fireripleytoy" - max_combat_health = 4 + max_combat_health = 5 //250 integrity? special_attack_type = SPECIAL_ATTACK_UTILITY special_attack_cry = "FIRE SHIELD" @@ -517,7 +519,7 @@ name = "toy Deathsquad Ripley" desc = "3/13" icon_state = "deathripleytoy" - max_combat_health = 5 + max_combat_health = 5 //250 integrity special_attack_type = SPECIAL_ATTACK_OTHER special_attack_type_message = "instantly destroys the opposing mech if its health is less than this mech's health." special_attack_cry = "KILLER CLAMP" @@ -534,7 +536,7 @@ name = "toy Gygax" desc = "4/13" icon_state = "gygaxtoy" - max_combat_health = 5 + max_combat_health = 5 //250 integrity special_attack_type = SPECIAL_ATTACK_UTILITY special_attack_cry = "SUPER SERVOS" @@ -542,7 +544,7 @@ name = "toy Durand" desc = "5/13" icon_state = "durandtoy" - max_combat_health = 6 + max_combat_health = 6 //400 integrity special_attack_type = SPECIAL_ATTACK_HEAL special_attack_cry = "SHIELD OF PROTECTION" @@ -550,21 +552,21 @@ name = "toy H.O.N.K." desc = "6/13" icon_state = "honktoy" - max_combat_health = 4 + max_combat_health = 4 //140 integrity special_attack_type = SPECIAL_ATTACK_OTHER special_attack_type_message = "puts the opposing mech's special move on cooldown and heals this mech." special_attack_cry = "MEGA HORN" /obj/item/toy/prize/honk/super_special_attack(obj/item/toy/prize/victim) playsound(src, 'sound/machines/honkbot_evil_laugh.ogg', 20, TRUE) - victim.special_attack_cooldown = 3 //Adds cooldown to the other mech and gives a minor self heal + victim.special_attack_cooldown += 3 //Adds cooldown to the other mech and gives a minor self heal combat_health++ /obj/item/toy/prize/marauder name = "toy Marauder" desc = "7/13" icon_state = "maraudertoy" - max_combat_health = 7 + max_combat_health = 7 //500 integrity special_attack_type = SPECIAL_ATTACK_DAMAGE special_attack_cry = "BEAM BLAST" @@ -572,7 +574,7 @@ name = "toy Seraph" desc = "8/13" icon_state = "seraphtoy" - max_combat_health = 8 + max_combat_health = 8 //550 integrity special_attack_type = SPECIAL_ATTACK_DAMAGE special_attack_cry = "ROCKET BARRAGE" @@ -580,7 +582,7 @@ name = "toy Mauler" desc = "9/13" icon_state = "maulertoy" - max_combat_health = 8 + max_combat_health = 7 //500 integrity special_attack_type = SPECIAL_ATTACK_DAMAGE special_attack_cry = "BULLET STORM" @@ -588,7 +590,7 @@ name = "toy Odysseus" desc = "10/13" icon_state = "odysseustoy" - max_combat_health = 4 + max_combat_health = 4 //120 integrity special_attack_type = SPECIAL_ATTACK_HEAL special_attack_cry = "MECHA BEAM" @@ -596,7 +598,7 @@ name = "toy Phazon" desc = "11/13" icon_state = "phazontoy" - max_combat_health = 6 + max_combat_health = 6 //200 integrity special_attack_type = SPECIAL_ATTACK_UTILITY special_attack_cry = "NO-CLIP" @@ -605,7 +607,7 @@ desc = "12/13" icon_state = "reticencetoy" quiet = TRUE - max_combat_health = 4 + max_combat_health = 4 //100 integrity special_attack_type = SPECIAL_ATTACK_OTHER special_attack_type_message = "has a lower cooldown than normal special moves, increases the opponent's cooldown, and deals damage." special_attack_cry = "*wave" @@ -619,7 +621,7 @@ name = "toy Clarke" desc = "13/13" icon_state = "clarketoy" - max_combat_health = 4 + max_combat_health = 4 //200 integrity special_attack_type = SPECIAL_ATTACK_UTILITY special_attack_cry = "ROLL OUT" From da23d44e82a4d03ea8a33c8c3026215115c57112 Mon Sep 17 00:00:00 2001 From: Tlaltecuhtli <33834933+Tlaltecuhtli@users.noreply.github.com> Date: Mon, 29 Jun 2020 22:13:36 +0200 Subject: [PATCH 015/196] 1 --- code/datums/wires/conveyor.dm | 17 +++++++++++++++++ code/modules/recycling/conveyor2.dm | 4 ++++ tgstation.dme | 1 + 3 files changed, 22 insertions(+) create mode 100644 code/datums/wires/conveyor.dm diff --git a/code/datums/wires/conveyor.dm b/code/datums/wires/conveyor.dm new file mode 100644 index 00000000000..26ade49d639 --- /dev/null +++ b/code/datums/wires/conveyor.dm @@ -0,0 +1,17 @@ +/datum/wires/conveyor + holder_type = /obj/machinery/conveyor_switch + proper_name = "conveyor" + var/mob/fingerman + +/datum/wires/conveyor/New(atom/holder) + add_duds(1) + ..() + +/datum/wires/conveyor/on_pulse(wire) + var/obj/machinery/conveyor_switch/C = holder + C.interact(fingerman) + +/datum/wires/conveyor/interactable(mob/user) + fingerman = user + return TRUE + diff --git a/code/modules/recycling/conveyor2.dm b/code/modules/recycling/conveyor2.dm index d7e70c7c953..94bb99c76ad 100644 --- a/code/modules/recycling/conveyor2.dm +++ b/code/modules/recycling/conveyor2.dm @@ -250,6 +250,7 @@ GLOBAL_LIST_EMPTY(conveyors_by_id) id = newid update_icon() LAZYADD(GLOB.conveyors_by_id[id], src) + wires = new /datum/wires/conveyor(src) /obj/machinery/conveyor_switch/Destroy() LAZYREMOVE(GLOB.conveyors_by_id[id], src) @@ -332,6 +333,9 @@ GLOBAL_LIST_EMPTY(conveyors_by_id) transfer_fingerprints_to(C) to_chat(user, "You detach the conveyor switch.") qdel(src) + if(is_wire_tool(I)) + wires.interact(user) + return TRUE /obj/machinery/conveyor_switch/oneway icon_state = "conveyor_switch_oneway" diff --git a/tgstation.dme b/tgstation.dme index 6c8d4195b9d..16fa201f827 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -635,6 +635,7 @@ #include "code\datums\wires\airlock.dm" #include "code\datums\wires\apc.dm" #include "code\datums\wires\autolathe.dm" +#include "code\datums\wires\conveyor.dm" #include "code\datums\wires\emitter.dm" #include "code\datums\wires\explosive.dm" #include "code\datums\wires\microwave.dm" From 686c56a959c9d84bbfe03e44a6b98e21341f99d9 Mon Sep 17 00:00:00 2001 From: Tlaltecuhtli <33834933+Tlaltecuhtli@users.noreply.github.com> Date: Wed, 1 Jul 2020 15:27:27 +0200 Subject: [PATCH 016/196] Update conveyor.dm --- code/datums/wires/conveyor.dm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/datums/wires/conveyor.dm b/code/datums/wires/conveyor.dm index 26ade49d639..0e82fb13cfb 100644 --- a/code/datums/wires/conveyor.dm +++ b/code/datums/wires/conveyor.dm @@ -1,6 +1,7 @@ /datum/wires/conveyor holder_type = /obj/machinery/conveyor_switch proper_name = "conveyor" + /// var holder that logs who put the assembly inside and gets transfered to the switch on pulse var/mob/fingerman /datum/wires/conveyor/New(atom/holder) @@ -14,4 +15,3 @@ /datum/wires/conveyor/interactable(mob/user) fingerman = user return TRUE - From fc53fa0211d15b6c344619ecc3dad3922451233f Mon Sep 17 00:00:00 2001 From: Unknown Date: Sat, 4 Jul 2020 01:22:22 +0200 Subject: [PATCH 017/196] i love dealing with this bullshit --- .../eldritch_cult/eldritch_effects.dm | 20 +++++++++++++++++-- .../eldritch_cult/eldritch_knowledge.dm | 4 ++-- .../living/simple_animal/eldritch_demons.dm | 4 +--- 3 files changed, 21 insertions(+), 7 deletions(-) diff --git a/code/modules/antagonists/eldritch_cult/eldritch_effects.dm b/code/modules/antagonists/eldritch_cult/eldritch_effects.dm index eac2c97d490..ba8fbe114c6 100644 --- a/code/modules/antagonists/eldritch_cult/eldritch_effects.dm +++ b/code/modules/antagonists/eldritch_cult/eldritch_effects.dm @@ -5,6 +5,8 @@ icon_state = "" resistance_flags = FIRE_PROOF | UNACIDABLE | ACID_PROOF layer = SIGIL_LAYER + ///Used mainly for summoning ritual to prevent spamming the rune to create millions of monsters. + var/list/atoms_in_use = list() /obj/effect/eldritch/attack_hand(mob/living/user) . = ..() @@ -15,7 +17,7 @@ /obj/effect/eldritch/proc/try_activate(mob/living/user) if(!IS_HERETIC(user)) return - activate(user) + INVOKE_ASYNC(src, .proc/activate , user) /obj/effect/eldritch/attacked_by(obj/item/I, mob/living/user) . = ..() @@ -39,7 +41,7 @@ if(living_in_range.stat != DEAD || living_in_range == user) // we only accept corpses, no living beings allowed. continue atoms_in_range += atom_in_range - + atoms_in_range -= atoms_in_use for(var/X in knowledge) var/datum/eldritch_knowledge/current_eldritch_knowledge = knowledge[X] @@ -70,8 +72,22 @@ flick("[icon_state]_active",src) playsound(user, 'sound/magic/castsummon.ogg', 75, TRUE) + atoms_in_use |= selected_atoms + for(var/to_disappear in atoms_in_use) + var/atom/atom_to_disappear = to_disappear + //temporary so we dont have to deal with the bs of someone picking those up when they may be deleted + atom_to_disappear.invisibility = INVISIBILITY_ABSTRACT if(current_eldritch_knowledge.on_finished_recipe(user,selected_atoms,loc)) current_eldritch_knowledge.cleanup_atoms(selected_atoms) + + listclearnulls() + + for(var/to_disappear in atoms_in_use) + var/atom/atom_to_disappear = to_disappear + //we need to reappear the item just in case the ritual didnt consume everything... or something. + atom_to_disappear.invisibility = initial(atom_to_disappear.invisibility) + + atoms_in_use = list() return to_chat(user,"Your ritual failed! You used either wrong components or are missing something important!") diff --git a/code/modules/antagonists/eldritch_cult/eldritch_knowledge.dm b/code/modules/antagonists/eldritch_cult/eldritch_knowledge.dm index 65dddf1c9c0..98f179042ef 100644 --- a/code/modules/antagonists/eldritch_cult/eldritch_knowledge.dm +++ b/code/modules/antagonists/eldritch_cult/eldritch_knowledge.dm @@ -188,8 +188,8 @@ var/datum/antagonist/heretic/master = user.mind.has_antag_datum(/datum/antagonist/heretic) heretic_monster.set_owner(master) //delaying the returning makes the cleanup_atoms break on the rune base, so we are calling it here. - cleanup_atoms(atoms) - return FALSE + //cleanup_atoms(atoms) + return TRUE //Ascension knowledge /datum/eldritch_knowledge/final diff --git a/code/modules/mob/living/simple_animal/eldritch_demons.dm b/code/modules/mob/living/simple_animal/eldritch_demons.dm index 2caee21d8af..bfec32b118c 100644 --- a/code/modules/mob/living/simple_animal/eldritch_demons.dm +++ b/code/modules/mob/living/simple_animal/eldritch_demons.dm @@ -16,7 +16,7 @@ speed = 0 a_intent = INTENT_HARM stop_automated_movement = 1 - AIStatus = AI_ON + AIStatus = AI_OFF attack_sound = 'sound/weapons/punch1.ogg' see_in_dark = 7 lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE @@ -37,8 +37,6 @@ /mob/living/simple_animal/hostile/eldritch/Initialize() . = ..() add_spells() - //by default - mind.add_antag_datum(/datum/antagonist/heretic_monster) /** * Add_spells From 8fa5710abf07381ecb9254771764d400beb4428e Mon Sep 17 00:00:00 2001 From: Unknown Date: Sat, 4 Jul 2020 01:33:24 +0200 Subject: [PATCH 018/196] E --- code/modules/antagonists/eldritch_cult/eldritch_effects.dm | 2 +- code/modules/antagonists/eldritch_cult/eldritch_knowledge.dm | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/code/modules/antagonists/eldritch_cult/eldritch_effects.dm b/code/modules/antagonists/eldritch_cult/eldritch_effects.dm index ba8fbe114c6..e82b15aa360 100644 --- a/code/modules/antagonists/eldritch_cult/eldritch_effects.dm +++ b/code/modules/antagonists/eldritch_cult/eldritch_effects.dm @@ -80,7 +80,7 @@ if(current_eldritch_knowledge.on_finished_recipe(user,selected_atoms,loc)) current_eldritch_knowledge.cleanup_atoms(selected_atoms) - listclearnulls() + listclearnulls(atoms_in_use) for(var/to_disappear in atoms_in_use) var/atom/atom_to_disappear = to_disappear diff --git a/code/modules/antagonists/eldritch_cult/eldritch_knowledge.dm b/code/modules/antagonists/eldritch_cult/eldritch_knowledge.dm index 98f179042ef..918e46a7cc2 100644 --- a/code/modules/antagonists/eldritch_cult/eldritch_knowledge.dm +++ b/code/modules/antagonists/eldritch_cult/eldritch_knowledge.dm @@ -187,8 +187,6 @@ var/datum/antagonist/heretic_monster/heretic_monster = summoned.mind.has_antag_datum(/datum/antagonist/heretic_monster) var/datum/antagonist/heretic/master = user.mind.has_antag_datum(/datum/antagonist/heretic) heretic_monster.set_owner(master) - //delaying the returning makes the cleanup_atoms break on the rune base, so we are calling it here. - //cleanup_atoms(atoms) return TRUE //Ascension knowledge From 0f7f57a0e9d7e80dd6a7ee54895d3e9e14343961 Mon Sep 17 00:00:00 2001 From: ATH1909 <42606352+ATH1909@users.noreply.github.com> Date: Sat, 4 Jul 2020 05:20:22 -0500 Subject: [PATCH 019/196] ooga chaka ooga ooga ooga chaka --- code/game/objects/structures/divine.dm | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/code/game/objects/structures/divine.dm b/code/game/objects/structures/divine.dm index d81a6ac4a86..5ce0f75850d 100644 --- a/code/game/objects/structures/divine.dm +++ b/code/game/objects/structures/divine.dm @@ -1,22 +1,22 @@ /obj/structure/sacrificealtar name = "sacrificial altar" - desc = "An altar designed to perform blood sacrifice for a deity." + desc = "An altar designed to perform blood sacrifice for a deity. Alt-click it to sacrifice a buckled creature." icon = 'icons/obj/hand_of_god_structures.dmi' icon_state = "sacrificealtar" anchored = TRUE density = FALSE can_buckle = 1 -/obj/structure/sacrificealtar/attack_hand(mob/living/user) - . = ..() - if(.) +/obj/structure/sacrificealtar/AltClick(mob/living/user) + ..() + if(!istype(user) || !user.canUseTopic(src, BE_CLOSE)) return if(!has_buckled_mobs()) return var/mob/living/L = locate() in buckled_mobs if(!L) return - to_chat(user, "You attempt to sacrifice [L] by invoking the sacrificial ritual.") + to_chat(user, "Invoking the sacred ritual, you sacrifice [L].") L.gib() message_admins("[ADMIN_LOOKUPFLW(user)] has sacrificed [key_name_admin(L)] on the sacrificial altar at [AREACOORD(src)].") From 2441a755f7b1e990403ebd2ad71987112434b866 Mon Sep 17 00:00:00 2001 From: Ryll-Ryll <3589655+Ryll-Ryll@users.noreply.github.com> Date: Sat, 4 Jul 2020 21:49:47 -0400 Subject: [PATCH 020/196] cronch --- code/datums/components/tackle.dm | 7 ++++--- code/game/atoms_movable.dm | 2 +- code/modules/vending/_vending.dm | 8 ++++++++ 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/code/datums/components/tackle.dm b/code/datums/components/tackle.dm index f20892cd36c..fa703ded705 100644 --- a/code/datums/components/tackle.dm +++ b/code/datums/components/tackle.dm @@ -366,6 +366,7 @@ user.adjustBruteLoss(30) playsound(user, 'sound/effects/blobattack.ogg', 60, TRUE) playsound(user, 'sound/effects/splat.ogg', 70, TRUE) + playsound(user, 'sound/effects/crack2.ogg', 70, TRUE) user.emote("scream") user.gain_trauma(/datum/brain_trauma/severe/paralysis/paraplegic) // oopsie indeed! shake_camera(user, 7, 7) @@ -434,13 +435,13 @@ W.obj_destruction() user.adjustStaminaLoss(10 * speed) user.Paralyze(30) - user.visible_message("[user] slams into [W] and shatters it, shredding [user.p_them()]self with glass!", "You slam into [W] and shatter it, shredding yourself with glass!") + user.visible_message("[user] smacks into [W] and shatters it, shredding [user.p_them()]self with glass!", "You smacks into [W] and shatter it, shredding yourself with glass!") else - user.visible_message("[user] slams into [W] like a bug, then slowly slides off it!", "You slam into [W] like a bug, then slowly slide off it!") + user.visible_message("[user] smacks into [W] like a bug!", "You smacks into [W] like a bug!") user.Paralyze(10) user.Knockdown(30) - W.take_damage(20 * speed) + W.take_damage(30 * speed) user.adjustStaminaLoss(10 * speed) user.adjustBruteLoss(5 * speed) diff --git a/code/game/atoms_movable.dm b/code/game/atoms_movable.dm index b5fd76c2e0a..f3292b260ab 100644 --- a/code/game/atoms_movable.dm +++ b/code/game/atoms_movable.dm @@ -574,7 +574,7 @@ if(impact_signal & COMPONENT_MOVABLE_IMPACT_FLIP_HITPUSH) hitpush = FALSE // hacky, tie this to something else or a proper workaround later - if(impact_signal & ~COMPONENT_MOVABLE_IMPACT_NEVERMIND) // in case a signal interceptor broke or deleted the thing before we could process our hit + if(!(impact_signal && (impact_signal & COMPONENT_MOVABLE_IMPACT_NEVERMIND))) // in case a signal interceptor broke or deleted the thing before we could process our hit return hit_atom.hitby(src, throwingdatum=throwingdatum, hitpush=hitpush) /atom/movable/hitby(atom/movable/AM, skipcatch, hitpush = TRUE, blocked, datum/thrownthing/throwingdatum) diff --git a/code/modules/vending/_vending.dm b/code/modules/vending/_vending.dm index f48e3decb07..9947cda61c8 100644 --- a/code/modules/vending/_vending.dm +++ b/code/modules/vending/_vending.dm @@ -962,6 +962,14 @@ GLOBAL_LIST_EMPTY(vending_products) /obj/machinery/vending/onTransitZ() return +/obj/machinery/vending/hitby(atom/movable/AM, skipcatch, hitpush, blocked, datum/thrownthing/throwingdatum) + . = ..() + var/mob/living/L = AM + if(tilted || !istype(L) || !prob(25 * (throwingdatum.speed - L.throw_speed))) // hulk throw = +25%, neckgrab throw = +25% + return + + tilt(L) + /obj/machinery/vending/custom name = "Custom Vendor" icon_state = "robotics" From ba39c73639e9f3d9477351e4b802c49e393eb0e0 Mon Sep 17 00:00:00 2001 From: Ryll-Ryll <3589655+Ryll-Ryll@users.noreply.github.com> Date: Sat, 4 Jul 2020 22:07:12 -0400 Subject: [PATCH 021/196] balance --- code/modules/mob/living/carbon/carbon.dm | 4 ++++ code/modules/vending/_vending.dm | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/code/modules/mob/living/carbon/carbon.dm b/code/modules/mob/living/carbon/carbon.dm index 20217535a0b..f6670cb401a 100644 --- a/code/modules/mob/living/carbon/carbon.dm +++ b/code/modules/mob/living/carbon/carbon.dm @@ -166,6 +166,10 @@ var/power_throw = 0 if(HAS_TRAIT(src, TRAIT_HULK)) power_throw++ + if(HAS_TRAIT(src, TRAIT_DWARF)) + power_throw-- + if(HAS_TRAIT(thrown_thing, TRAIT_DWARF)) + power_throw++ if(pulling && grab_state >= GRAB_NECK) power_throw++ visible_message("[src] throws [thrown_thing][power_throw ? " really hard!" : "."]", \ diff --git a/code/modules/vending/_vending.dm b/code/modules/vending/_vending.dm index 9947cda61c8..6e5e6a07068 100644 --- a/code/modules/vending/_vending.dm +++ b/code/modules/vending/_vending.dm @@ -965,7 +965,7 @@ GLOBAL_LIST_EMPTY(vending_products) /obj/machinery/vending/hitby(atom/movable/AM, skipcatch, hitpush, blocked, datum/thrownthing/throwingdatum) . = ..() var/mob/living/L = AM - if(tilted || !istype(L) || !prob(25 * (throwingdatum.speed - L.throw_speed))) // hulk throw = +25%, neckgrab throw = +25% + if(tilted || !istype(L) || !prob(20 * (throwingdatum.speed - L.throw_speed))) // hulk throw = +20%, neckgrab throw = +20% return tilt(L) From 473bae0a208a671f0eed6a54d7ddc2928c3b934f Mon Sep 17 00:00:00 2001 From: Ryll-Ryll <3589655+Ryll-Ryll@users.noreply.github.com> Date: Sat, 4 Jul 2020 23:07:35 -0400 Subject: [PATCH 022/196] can set speed --- code/__DEFINES/misc.dm | 6 ++++ code/modules/recycling/disposal/outlet.dm | 42 ++++++++++++++++++++++- 2 files changed, 47 insertions(+), 1 deletion(-) diff --git a/code/__DEFINES/misc.dm b/code/__DEFINES/misc.dm index 2a807aecd47..3ac969e4cd4 100644 --- a/code/__DEFINES/misc.dm +++ b/code/__DEFINES/misc.dm @@ -120,6 +120,12 @@ #define SHOES_TIED 1 #define SHOES_KNOTTED 2 +//how fast a disposal machinery thing is ejecting things +#define EJECT_SPEED_SLOW 1 +#define EJECT_SPEED_MED 2 +#define EJECT_SPEED_FAST 4 +#define EJECT_SPEED_YEET 6 + //Cache of bloody footprint images //Key: //"entered-[blood_state]-[dir_of_image]" diff --git a/code/modules/recycling/disposal/outlet.dm b/code/modules/recycling/disposal/outlet.dm index 7f15938ca68..5da04b70c3a 100644 --- a/code/modules/recycling/disposal/outlet.dm +++ b/code/modules/recycling/disposal/outlet.dm @@ -13,6 +13,10 @@ var/obj/structure/disposalconstruct/stored var/start_eject = 0 var/eject_range = 2 + /// how fast we're spitting fir- atoms + var/eject_speed = EJECT_SPEED_MED + /// if we've been emagged or not, duh + var/emagged = FALSE /obj/structure/disposaloutlet/Initialize(mapload, obj/structure/disposalconstruct/make_from) . = ..() @@ -61,7 +65,7 @@ var/atom/movable/AM = A AM.forceMove(T) AM.pipe_eject(dir) - AM.throw_at(target, eject_range, 1) + AM.throw_at(target, eject_range, eject_speed) H.vent_gas(T) qdel(H) @@ -80,3 +84,39 @@ stored = null qdel(src) return TRUE + +/obj/structure/disposaloutlet/examine(mob/user) + . = ..() + switch(eject_speed) + if(EJECT_SPEED_SLOW) + . += "An LED image of a turtle is displayed on the side of the outlet." + if(EJECT_SPEED_MED) + . += "An LED image of a bumblebee is displayed on the side of the outlet." + if(EJECT_SPEED_FAST) + . += "An LED image of a speeding bullet is displayed on the side of the outlet." + if(EJECT_SPEED_YEET) + . += "An LED image of a grawlix is displayed on the side of the outlet." + +/obj/structure/disposaloutlet/multitool_act(mob/living/user, obj/item/I) + . = ..() + to_chat(user, "You adjust the ejection force on \the [src].") + switch(eject_speed) + if(EJECT_SPEED_SLOW) + eject_speed = EJECT_SPEED_MED + if(EJECT_SPEED_MED) + eject_speed = EJECT_SPEED_FAST + if(EJECT_SPEED_FAST) + if(emagged) + eject_speed = EJECT_SPEED_YEET + else + eject_speed = EJECT_SPEED_SLOW + if(EJECT_SPEED_YEET) + eject_speed = EJECT_SPEED_SLOW + return TRUE + +/obj/structure/disposaloutlet/emag_act(mob/user, obj/item/card/emag/E) + . = ..() + if(emagged) + return + to_chat(user, "You silently disable the sanity checking on \the [src]'s ejection force.") + emagged = TRUE From 11eba5bf94973667db3c5ddd72bbc20b8b4c0e1b Mon Sep 17 00:00:00 2001 From: Tlaltecuhtli <33834933+Tlaltecuhtli@users.noreply.github.com> Date: Mon, 6 Jul 2020 14:03:38 +0200 Subject: [PATCH 023/196] 1 --- code/controllers/subsystem/vote.dm | 1 + 1 file changed, 1 insertion(+) diff --git a/code/controllers/subsystem/vote.dm b/code/controllers/subsystem/vote.dm index 430e9b6d45e..f3255098410 100644 --- a/code/controllers/subsystem/vote.dm +++ b/code/controllers/subsystem/vote.dm @@ -229,6 +229,7 @@ SUBSYSTEM_DEF(vote) C.player_details.player_actions += V V.Grant(C.mob) generated_actions += V + SEND_SOUND(C, sound('sound/machines/nuke/angry_beep.ogg')) return TRUE return FALSE From f0d1d6c5391c603ee49451c83fb8947b9ba0d20a Mon Sep 17 00:00:00 2001 From: Unknown Date: Mon, 6 Jul 2020 14:51:49 +0200 Subject: [PATCH 024/196] ascension --- .../antagonists/eldritch_cult/eldritch_antag.dm | 11 +++++++---- .../antagonists/eldritch_cult/knowledge/ash_lore.dm | 2 ++ .../antagonists/eldritch_cult/knowledge/flesh_lore.dm | 4 ++++ .../antagonists/eldritch_cult/knowledge/rust_lore.dm | 2 ++ 4 files changed, 15 insertions(+), 4 deletions(-) diff --git a/code/modules/antagonists/eldritch_cult/eldritch_antag.dm b/code/modules/antagonists/eldritch_cult/eldritch_antag.dm index e28174ea994..b5ca7c2c330 100644 --- a/code/modules/antagonists/eldritch_cult/eldritch_antag.dm +++ b/code/modules/antagonists/eldritch_cult/eldritch_antag.dm @@ -9,6 +9,7 @@ var/give_equipment = TRUE var/list/researched_knowledge = list() var/total_sacrifices = 0 + var/ascended = FALSE /datum/antagonist/heretic/admin_add(datum/mind/new_owner,mob/admin) give_equipment = FALSE @@ -159,11 +160,13 @@ parts += "Objective #[count]: [objective.explanation_text] Fail." cultiewin = FALSE count++ - - if(cultiewin) - parts += "The heretic was successful!" + if(ascended) + parts += "HERETIC HAS ASCENDED!" else - parts += "The heretic has failed." + if(cultiewin) + parts += "The heretic was successful!" + else + parts += "The heretic has failed." parts += "Knowledge Researched: " diff --git a/code/modules/antagonists/eldritch_cult/knowledge/ash_lore.dm b/code/modules/antagonists/eldritch_cult/knowledge/ash_lore.dm index d220871b918..fc8a30c08bc 100644 --- a/code/modules/antagonists/eldritch_cult/knowledge/ash_lore.dm +++ b/code/modules/antagonists/eldritch_cult/knowledge/ash_lore.dm @@ -176,6 +176,8 @@ var/mob/living/carbon/human/H = user H.physiology.brute_mod *= 0.5 H.physiology.burn_mod *= 0.5 + var/datum/antagonist/heretic/ascension = H.mind.has_antag_datum(/datum/antagonist/heretic) + ascension.ascended = TRUE for(var/X in trait_list) ADD_TRAIT(user,X,MAGIC_TRAIT) return ..() diff --git a/code/modules/antagonists/eldritch_cult/knowledge/flesh_lore.dm b/code/modules/antagonists/eldritch_cult/knowledge/flesh_lore.dm index 6ff9a54fbd9..3d579853ba9 100644 --- a/code/modules/antagonists/eldritch_cult/knowledge/flesh_lore.dm +++ b/code/modules/antagonists/eldritch_cult/knowledge/flesh_lore.dm @@ -236,6 +236,7 @@ var/datum/antagonist/heretic_monster/monster = summoned.mind.has_antag_datum(/datum/antagonist/heretic_monster) var/datum/antagonist/heretic/master = user.mind.has_antag_datum(/datum/antagonist/heretic) monster.set_owner(master) + master.ascended = TRUE if("Yes") var/mob/living/summoned = new /mob/living/simple_animal/hostile/eldritch/armsy/prime(loc,TRUE,10) summoned.ghostize(0) @@ -243,6 +244,9 @@ priority_announce("$^@&#*$^@(#&$(@&#^$&#^@# Fear the dark, for king of arms has ascended! Lord of the night has come! $^@&#*$^@(#&$(@&#^$&#^@#","#$^@&#*$^@(#&$(@&#^$&#^@#", 'sound/ai/spanomalies.ogg') log_game("[user.real_name] ascended as [summoned.real_name]") var/mob/living/carbon/carbon_user = user + var/datum/antagonist/heretic/ascension = carbon_user.mind.has_antag_datum(/datum/antagonist/heretic) + ascension.ascended = TRUE carbon_user.mind.transfer_to(summoned, TRUE) carbon_user.gib() + return ..() diff --git a/code/modules/antagonists/eldritch_cult/knowledge/rust_lore.dm b/code/modules/antagonists/eldritch_cult/knowledge/rust_lore.dm index 8435fb867c1..e59b098a8db 100644 --- a/code/modules/antagonists/eldritch_cult/knowledge/rust_lore.dm +++ b/code/modules/antagonists/eldritch_cult/knowledge/rust_lore.dm @@ -130,6 +130,8 @@ H.physiology.burn_mod *= 0.5 priority_announce("$^@&#*$^@(#&$(@&#^$&#^@# Fear the decay, for Rustbringer [user.real_name] has come! $^@&#*$^@(#&$(@&#^$&#^@#","#$^@&#*$^@(#&$(@&#^$&#^@#", 'sound/ai/spanomalies.ogg') new /datum/rust_spread(loc) + var/datum/antagonist/heretic/ascension = H.mind.has_antag_datum(/datum/antagonist/heretic) + ascension.ascended = TRUE return ..() From 0852f1b80f0cdcf60d236ddec912aa1de57d9401 Mon Sep 17 00:00:00 2001 From: Wayland-Smithy <64715958+Wayland-Smithy@users.noreply.github.com> Date: Mon, 6 Jul 2020 09:39:57 -0400 Subject: [PATCH 025/196] blob strain honesty --- code/modules/antagonists/blob/blobstrains/reactive_spines.dm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/code/modules/antagonists/blob/blobstrains/reactive_spines.dm b/code/modules/antagonists/blob/blobstrains/reactive_spines.dm index c3c80c08cbf..dd68ff63ff0 100644 --- a/code/modules/antagonists/blob/blobstrains/reactive_spines.dm +++ b/code/modules/antagonists/blob/blobstrains/reactive_spines.dm @@ -4,7 +4,7 @@ description = "will do medium brute damage through armor and bio resistance." effectdesc = "will also react when attacked with close up burn or brute damage, attacking all near the attacked blob." analyzerdescdamage = "Does medium brute damage, ignoring armor and bio resistance." - analyzerdesceffect = "When attacked with brute damage, will lash out, attacking everything near it." + analyzerdesceffect = "When attacked with burn or brute damage it violently lashes out, attacking everything nearby." color = "#9ACD32" complementary_color = "#FFA500" blobbernaut_message = "stabs" @@ -12,7 +12,7 @@ reagent = /datum/reagent/blob/reactive_spines /datum/blobstrain/reagent/reactive_spines/damage_reaction(obj/structure/blob/B, damage, damage_type, damage_flag) - if(damage && ((damage_type == BRUTE) || (damage_type == BURN)) && B.obj_integrity - damage > 0) //is there any damage, is it brute, and will we be alive + if(damage && ((damage_type == BRUTE) || (damage_type == BURN)) && B.obj_integrity - damage > 0) //is there any damage, is it burn or brute, and will we be alive if(damage_flag == "melee") B.visible_message("The blob retaliates, lashing out!") for(var/atom/A in range(1, B)) From 0515b7be8484d492711485ad64318d8955582a3e Mon Sep 17 00:00:00 2001 From: Tlaltecuhtli <33834933+Tlaltecuhtli@users.noreply.github.com> Date: Mon, 6 Jul 2020 16:18:12 +0200 Subject: [PATCH 026/196] tooggle --- code/controllers/subsystem/vote.dm | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/code/controllers/subsystem/vote.dm b/code/controllers/subsystem/vote.dm index f3255098410..a86a8f71e21 100644 --- a/code/controllers/subsystem/vote.dm +++ b/code/controllers/subsystem/vote.dm @@ -229,7 +229,8 @@ SUBSYSTEM_DEF(vote) C.player_details.player_actions += V V.Grant(C.mob) generated_actions += V - SEND_SOUND(C, sound('sound/machines/nuke/angry_beep.ogg')) + if(C.prefs.toggles & SOUND_ANNOUNCEMENTS) + SEND_SOUND(C, sound('sound/machines/nuke/angry_beep.ogg')) return TRUE return FALSE From 79db40e2045ff344db1f7f31995c72f8cc31b241 Mon Sep 17 00:00:00 2001 From: Unknown Date: Mon, 6 Jul 2020 23:21:58 +0200 Subject: [PATCH 027/196] E --- .../eldritch_cult/eldritch_effects.dm | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/code/modules/antagonists/eldritch_cult/eldritch_effects.dm b/code/modules/antagonists/eldritch_cult/eldritch_effects.dm index e82b15aa360..a305d3e05d3 100644 --- a/code/modules/antagonists/eldritch_cult/eldritch_effects.dm +++ b/code/modules/antagonists/eldritch_cult/eldritch_effects.dm @@ -6,7 +6,7 @@ resistance_flags = FIRE_PROOF | UNACIDABLE | ACID_PROOF layer = SIGIL_LAYER ///Used mainly for summoning ritual to prevent spamming the rune to create millions of monsters. - var/list/atoms_in_use = list() + var/is_in_use = FALSE /obj/effect/eldritch/attack_hand(mob/living/user) . = ..() @@ -17,7 +17,8 @@ /obj/effect/eldritch/proc/try_activate(mob/living/user) if(!IS_HERETIC(user)) return - INVOKE_ASYNC(src, .proc/activate , user) + if(!is_in_use) + INVOKE_ASYNC(src, .proc/activate , user) /obj/effect/eldritch/attacked_by(obj/item/I, mob/living/user) . = ..() @@ -25,6 +26,7 @@ qdel(src) /obj/effect/eldritch/proc/activate(mob/living/user) + is_in_use = TRUE // Have fun trying to read this proc. var/datum/antagonist/heretic/cultie = user.mind.has_antag_datum(/datum/antagonist/heretic) var/list/knowledge = cultie.get_all_knowledge() @@ -72,23 +74,23 @@ flick("[icon_state]_active",src) playsound(user, 'sound/magic/castsummon.ogg', 75, TRUE) - atoms_in_use |= selected_atoms - for(var/to_disappear in atoms_in_use) + for(var/to_disappear in selected_atoms) var/atom/atom_to_disappear = to_disappear //temporary so we dont have to deal with the bs of someone picking those up when they may be deleted atom_to_disappear.invisibility = INVISIBILITY_ABSTRACT if(current_eldritch_knowledge.on_finished_recipe(user,selected_atoms,loc)) current_eldritch_knowledge.cleanup_atoms(selected_atoms) + is_in_use = FALSE - listclearnulls(atoms_in_use) + listclearnulls(selected_atoms) - for(var/to_disappear in atoms_in_use) + for(var/to_disappear in selected_atoms) var/atom/atom_to_disappear = to_disappear //we need to reappear the item just in case the ritual didnt consume everything... or something. atom_to_disappear.invisibility = initial(atom_to_disappear.invisibility) - atoms_in_use = list() return + is_in_use = FALSE to_chat(user,"Your ritual failed! You used either wrong components or are missing something important!") /obj/effect/eldritch/big From c1a1aaa642ba4ee6a716f602b9b4058d6a78c46d Mon Sep 17 00:00:00 2001 From: ShizCalev Date: Mon, 6 Jul 2020 20:12:12 -0400 Subject: [PATCH 028/196] better stack amount varedit handling --- code/game/objects/items/stacks/stack.dm | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/code/game/objects/items/stacks/stack.dm b/code/game/objects/items/stacks/stack.dm index 0ea8b83e065..0099c33e6ec 100644 --- a/code/game/objects/items/stacks/stack.dm +++ b/code/game/objects/items/stacks/stack.dm @@ -275,6 +275,17 @@ qdel(I) //BubbleWrap END +/obj/item/stack/vv_edit_var(vname, vval) + if(vname == NAMEOF(src, amount)) + add(clamp(vval, 1-amount, max_amount - amount)) //there must always be one. + return TRUE + else if(vname == NAMEOF(src, max_amount)) + max_amount = max(vval, 1) + add((max_amount < amount) ? (max_amount - amount) : 0) //update icon, weight, ect + return TRUE + return ..() + + /obj/item/stack/proc/building_checks(datum/stack_recipe/R, multiplier) if (get_amount() < R.req_amount*multiplier) if (R.req_amount*multiplier>1) From 719d3a1f452b8e7b97bcfb972fb15abdc3f93e2a Mon Sep 17 00:00:00 2001 From: MrDoomBringer Date: Mon, 6 Jul 2020 23:40:21 -0400 Subject: [PATCH 029/196] first pass WIP overlays icon upd8 more need to figure out whats going on with extractionpods compile the icon update more!! why are skillcapes broken boat lmao seethrough fix Second drive-by Code cleanup and improvements Specifically surrounding contractor pods and reverse mode working properly accidently left in an extra dmi whups do the impossible see the invisible new effect booster pack MFW MRW Linter fail --- code/__DEFINES/cargo.dm | 46 +- code/datums/skills/_skill.dm | 2 +- code/game/atoms.dm | 16 + code/game/machinery/roulette_machine.dm | 2 +- code/game/objects/items/miscellaneous.dm | 2 +- .../structures/crates_lockers/closets.dm | 9 +- code/modules/admin/admin.dm | 2 +- code/modules/admin/topic.dm | 2 +- code/modules/admin/verbs/randomverbs.dm | 2 +- .../traitor/equipment/contractor.dm | 2 +- .../antagonists/traitor/syndicate_contract.dm | 6 +- code/modules/cargo/centcom_podlauncher.dm | 8 +- code/modules/cargo/expressconsole.dm | 4 +- code/modules/cargo/gondolapod.dm | 20 +- code/modules/cargo/supplypod.dm | 454 +++++++++++++----- code/modules/events/stray_cargo.dm | 2 +- icons/mob/gondolapod.dmi | Bin 1339 -> 1191 bytes icons/obj/supplypods.dmi | Bin 56060 -> 26572 bytes icons/obj/supplypods_32x32.dmi | Bin 0 -> 1640 bytes 19 files changed, 407 insertions(+), 172 deletions(-) create mode 100644 icons/obj/supplypods_32x32.dmi diff --git a/code/__DEFINES/cargo.dm b/code/__DEFINES/cargo.dm index 6b6c1325972..2939952f863 100644 --- a/code/__DEFINES/cargo.dm +++ b/code/__DEFINES/cargo.dm @@ -13,23 +13,35 @@ #define STYLE_GONDOLA 13 #define STYLE_SEETHROUGH 14 -#define POD_ICON_STATE 1 -#define POD_NAME 2 -#define POD_DESC 3 +#define POD_SHAPE 1 +#define POD_BASE 2 +#define POD_DECAL 3 +#define POD_GLOW 4 +#define POD_RUBBLE_TYPE 5 +#define POD_NAME 6 +#define POD_DESC 7 + +#define RUBBLE_NONE 1 +#define RUBBLE_NORMAL 2 +#define RUBBLE_WIDE 3 +#define RUBBLE_THIN 4 + +#define POD_SHAPE_NORML 1 +#define POD_SHAPE_OTHER 2 #define POD_STYLES list(\ - list("supplypod", "supply pod", "A Nanotrasen supply drop pod."),\ - list("bluespacepod", "bluespace supply pod" , "A Nanotrasen Bluespace supply pod. Teleports back to CentCom after delivery."),\ - list("centcompod", "\improper CentCom supply pod", "A Nanotrasen supply pod, this one has been marked with Central Command's designations. Teleports back to CentCom after delivery."),\ - list("syndiepod", "blood-red supply pod", "A dark, intimidating supply pod, covered in the blood-red markings of the Syndicate. It's probably best to stand back from this."),\ - list("squadpod", "\improper MK. II supply pod", "A Nanotrasen supply pod. This one has been marked the markings of some sort of elite strike team."),\ - list("cultpod", "bloody supply pod", "A Nanotrasen supply pod covered in scratch-marks, blood, and strange runes."),\ - list("missilepod", "cruise missile", "A big ass missile that didn't seem to fully detonate. It was likely launched from some far-off deep space missile silo. There appears to be an auxiliary payload hatch on the side, though manually opening it is likely impossible."),\ - list("smissilepod", "\improper Syndicate cruise missile", "A big ass, blood-red missile that didn't seem to fully detonate. It was likely launched from some deep space Syndicate missile silo. There appears to be an auxiliary payload hatch on the side, though manually opening it is likely impossible."),\ - list("boxpod", "\improper Aussec supply crate", "An incredibly sturdy supply crate, designed to withstand orbital re-entry. Has 'Aussec Armory - 2532' engraved on the side."),\ - list("honkpod", "\improper HONK pod", "A brightly-colored supply pod. It likely originated from the Clown Federation."),\ - list("fruitpod", "\improper Orange", "An angry orange."),\ - list("", "\improper S.T.E.A.L.T.H. pod MKVII", "A supply pod that, under normal circumstances, is completely invisible to conventional methods of detection. How are you even seeing this?"),\ - list("gondolapod", "gondola", "The silent walker. This one seems to be part of a delivery agency."),\ - list("", "", "")\ + list(POD_SHAPE_NORML, "pod", "default", "yellow", RUBBLE_NORMAL, "supply pod", "A Nanotrasen supply drop pod."),\ + list(POD_SHAPE_NORML, "advpod", "bluespace", "blue", RUBBLE_NORMAL, "bluespace supply pod" , "A Nanotrasen Bluespace supply pod. Teleports back to CentCom after delivery."),\ + list(POD_SHAPE_NORML, "advpod", "centcom", "blue", RUBBLE_NORMAL, "\improper CentCom supply pod", "A Nanotrasen supply pod, this one has been marked with Central Command's designations. Teleports back to CentCom after delivery."),\ + list(POD_SHAPE_NORML, "darkpod", "syndicate", "red", RUBBLE_NORMAL, "blood-red supply pod", "An intimidating supply pod, covered in the blood-red markings of the Syndicate. It's probably best to stand back from this."),\ + list(POD_SHAPE_NORML, "darkpod", "deathsquad", "blue", RUBBLE_NORMAL, "\improper Deathsquad drop pod", "A Nanotrasen drop pod. This one has been marked the markings of Nanotrasen's elite strike team."),\ + list(POD_SHAPE_NORML, "pod", "cultist", "red", RUBBLE_NORMAL, "bloody supply pod", "A Nanotrasen supply pod covered in scratch-marks, blood, and strange runes."),\ + list(POD_SHAPE_OTHER, "missile", null, "yellow", RUBBLE_THIN, "cruise missile", "A big ass missile that didn't seem to fully detonate. It was likely launched from some far-off deep space missile silo. There appears to be an auxillery payload hatch on the side, though manually opening it is likely impossible."),\ + list(POD_SHAPE_OTHER, "smissile", null, "yellow", RUBBLE_THIN, "\improper Syndicate cruise missile", "A big ass, blood-red missile that didn't seem to fully detonate. It was likely launched from some deep space Syndicate missile silo. There appears to be an auxillery payload hatch on the side, though manually opening it is likely impossible."),\ + list(POD_SHAPE_OTHER, "box", null, "yellow", RUBBLE_WIDE, "\improper Aussec supply crate", "An incredibly sturdy supply crate, designed to withstand orbital re-entry. Has 'Aussec Armory - 2532' engraved on the side."),\ + list(POD_SHAPE_NORML, "clownpod", "clown", "green", RUBBLE_NORMAL, "\improper HONK pod", "A brightly-colored supply pod. It likely originated from the Clown Federation."),\ + list(POD_SHAPE_OTHER, "orange", null, "yellow", RUBBLE_NONE, "\improper Orange", "An angry orange."),\ + list(POD_SHAPE_OTHER, "", null, "yellow", RUBBLE_NONE, "\improper S.T.E.A.L.T.H. pod MKVII", "A supply pod that, under normal circumstances, is completely invisible to conventional methods of detection. How are you even seeing this?"),\ + list(POD_SHAPE_OTHER, "gondola", null, "yellow", RUBBLE_NONE, "gondola", "The silent walker. This one seems to be part of a delivery agency."),\ + list(POD_SHAPE_OTHER, "", null, "yellow", RUBBLE_NONE, "", "")\ ) diff --git a/code/datums/skills/_skill.dm b/code/datums/skills/_skill.dm index c7a434524c7..459762c07be 100644 --- a/code/datums/skills/_skill.dm +++ b/code/datums/skills/_skill.dm @@ -77,5 +77,5 @@ GLOBAL_LIST_INIT(skill_types, subtypesof(/datum/skill)) pod.explosionSize = list(0,0,0,0) to_chat(mind.current, "My legendary skill has attracted the attention of the Professional [title] Association. It seems they are sending me a status symbol to commemorate my abilities.") var/turf/T = get_turf(mind.current) - new /obj/effect/dp_target(T, pod , new skill_cape_path(T)) + new /obj/effect/pod_landingzone(T, pod , new skill_cape_path(T)) LAZYADD(mind.skills_rewarded, src.type) diff --git a/code/game/atoms.dm b/code/game/atoms.dm index 15282d346da..dbc801e42da 100644 --- a/code/game/atoms.dm +++ b/code/game/atoms.dm @@ -1409,3 +1409,19 @@ */ /atom/proc/rust_heretic_act() return + +/** + * Used to set something as 'open' if it's being used as a supplypod + * + * Override this if you want an atom to be usable as a supplypod. + */ +/atom/proc/setOpened() + return + +/** + * Used to set something as 'closed' if it's being used as a supplypod + * + * Override this if you want an atom to be usable as a supplypod. + */ +/atom/proc/setClosed() + return diff --git a/code/game/machinery/roulette_machine.dm b/code/game/machinery/roulette_machine.dm index 8023b366a5b..61bd80508e9 100644 --- a/code/game/machinery/roulette_machine.dm +++ b/code/game/machinery/roulette_machine.dm @@ -409,7 +409,7 @@ new /obj/machinery/roulette(toLaunch) - new /obj/effect/dp_target(drop_location(), toLaunch) + new /obj/effect/pod_landingzone(drop_location(), toLaunch) qdel(src) #undef ROULETTE_SINGLES_PAYOUT diff --git a/code/game/objects/items/miscellaneous.dm b/code/game/objects/items/miscellaneous.dm index 2fde0d20022..874a31f6726 100644 --- a/code/game/objects/items/miscellaneous.dm +++ b/code/game/objects/items/miscellaneous.dm @@ -61,7 +61,7 @@ msg = "You hear something crackle in your ears for a moment before a voice speaks. \"Please stand by for a message from Central Command. Message as follows: Item request received. Your package is inbound, please stand back from the landing site. Message ends.\"" to_chat(M, msg) - new /obj/effect/dp_target(get_turf(src), pod) + new /obj/effect/pod_landingzone(get_turf(src), pod) /obj/item/choice_beacon/hero name = "heroic beacon" diff --git a/code/game/objects/structures/crates_lockers/closets.dm b/code/game/objects/structures/crates_lockers/closets.dm index eb8de6fa75e..a9cd249e288 100644 --- a/code/game/objects/structures/crates_lockers/closets.dm +++ b/code/game/objects/structures/crates_lockers/closets.dm @@ -61,6 +61,8 @@ /obj/structure/closet/update_icon() . = ..() + if (istype(src, /obj/structure/closet/supplypod)) + return if(!opened) layer = OBJ_LAYER else @@ -337,8 +339,11 @@ var/mob/living/L = O if(!issilicon(L)) L.Paralyze(40) - O.forceMove(T) - close() + if(istype(src, /obj/structure/closet/supplypod/extractionpod)) + O.forceMove(src) + else + O.forceMove(T) + close() else O.forceMove(T) return 1 diff --git a/code/modules/admin/admin.dm b/code/modules/admin/admin.dm index 951daece2d3..e2eb887d05e 100644 --- a/code/modules/admin/admin.dm +++ b/code/modules/admin/admin.dm @@ -717,7 +717,7 @@ var/obj/structure/closet/supplypod/centcompod/pod = new() var/atom/A = new chosen(pod) A.flags_1 |= ADMIN_SPAWNED_1 - new /obj/effect/dp_target(T, pod) + new /obj/effect/pod_landingzone(T, pod) log_admin("[key_name(usr)] pod-spawned [chosen] at [AREACOORD(usr)]") SSblackbox.record_feedback("tally", "admin_verb", 1, "Podspawn Atom") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! diff --git a/code/modules/admin/topic.dm b/code/modules/admin/topic.dm index 12cc7d03783..ccc0d86fffe 100644 --- a/code/modules/admin/topic.dm +++ b/code/modules/admin/topic.dm @@ -1701,7 +1701,7 @@ R.activate_module(I) if(pod) - new /obj/effect/dp_target(target, pod) + new /obj/effect/pod_landingzone(target, pod) if (number == 1) log_admin("[key_name(usr)] created a [english_list(paths)]") diff --git a/code/modules/admin/verbs/randomverbs.dm b/code/modules/admin/verbs/randomverbs.dm index 6598df12c60..12b67eeec08 100644 --- a/code/modules/admin/verbs/randomverbs.dm +++ b/code/modules/admin/verbs/randomverbs.dm @@ -1116,7 +1116,7 @@ Traitors and the like can also be revived with the previous role mostly intact. alert("ERROR: Incorrect / improper path given.") return new delivery(pod) - new /obj/effect/dp_target(get_turf(target), pod) + new /obj/effect/pod_landingzone(get_turf(target), pod) if(ADMIN_PUNISHMENT_SUPPLYPOD) var/datum/centcom_podlauncher/plaunch = new(usr) if(!holder) diff --git a/code/modules/antagonists/traitor/equipment/contractor.dm b/code/modules/antagonists/traitor/equipment/contractor.dm index cd72eb4957d..5b9b6183601 100644 --- a/code/modules/antagonists/traitor/equipment/contractor.dm +++ b/code/modules/antagonists/traitor/equipment/contractor.dm @@ -229,7 +229,7 @@ to_chat(partner_mind.current, "\n[user.real_name] is your superior. Follow any, and all orders given by them. You're here to support their mission only.") to_chat(partner_mind.current, "Should they perish, or be otherwise unavailable, you're to assist other active agents in this mission area to the best of your ability.\n\n") - new /obj/effect/dp_target(free_location, arrival_pod) + new /obj/effect/pod_landingzone(free_location, arrival_pod) /datum/contractor_item/blackout name = "Blackout" diff --git a/code/modules/antagonists/traitor/syndicate_contract.dm b/code/modules/antagonists/traitor/syndicate_contract.dm index 4936aca541c..8a6d633a668 100644 --- a/code/modules/antagonists/traitor/syndicate_contract.dm +++ b/code/modules/antagonists/traitor/syndicate_contract.dm @@ -68,7 +68,7 @@ empty_pod.explosionSize = list(0,0,0,1) empty_pod.leavingSound = 'sound/effects/podwoosh.ogg' - new /obj/effect/dp_target(empty_pod_turf, empty_pod) + new /obj/effect/pod_landingzone(empty_pod_turf, empty_pod) /datum/syndicate_contract/proc/enter_check(datum/source, sent_mob) if (istype(source, /obj/structure/closet/supplypod/extractionpod)) @@ -111,7 +111,7 @@ var/obj/structure/closet/supplypod/extractionpod/pod = source // Handle the pod returning - pod.send_up(pod) + pod.depart(pod) if (ishuman(M)) var/mob/living/carbon/human/target = M @@ -226,7 +226,7 @@ M.Dizzy(35) M.confused += 20 - new /obj/effect/dp_target(possible_drop_loc[pod_rand_loc], return_pod) + new /obj/effect/pod_landingzone(possible_drop_loc[pod_rand_loc], return_pod) else to_chat(M, "A million voices echo in your head... \"Seems where you got sent here from won't \ be able to handle our pod... You will die here instead.\"") diff --git a/code/modules/cargo/centcom_podlauncher.dm b/code/modules/cargo/centcom_podlauncher.dm index 48db4feb9d6..5fe8d968920 100644 --- a/code/modules/cargo/centcom_podlauncher.dm +++ b/code/modules/cargo/centcom_podlauncher.dm @@ -19,7 +19,7 @@ //Variables declared to change how items in the launch bay are picked and launched. (Almost) all of these are changed in the ui_act proc //Some effect groups are choices, while other are booleans. This is because some effects can stack, while others dont (ex: you can stack explosion and quiet, but you cant stack ordered launch and random launch) /datum/centcom_podlauncher - var/static/list/ignored_atoms = typecacheof(list(null, /mob/dead, /obj/effect/landmark, /obj/docking_port, /atom/movable/lighting_object, /obj/effect/particle_effect/sparks, /obj/effect/dp_target, /obj/effect/supplypod_selector )) + var/static/list/ignored_atoms = typecacheof(list(null, /mob/dead, /obj/effect/landmark, /obj/docking_port, /atom/movable/lighting_object, /obj/effect/particle_effect/sparks, /obj/effect/pod_landingzone, /obj/effect/supplypod_selector )) var/turf/oldTurf //Keeps track of where the user was at if they use the "teleport to centcom" button, so they can go back var/client/holder //client of whoever is using this datum var/area/bay //What bay we're using to launch shit from. @@ -135,8 +135,8 @@ force_open = FALSE, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.adm return //Only teleport if the list isn't empty var/turf/T = pick(turfs) M.forceMove(T) //Perform the actual teleport - log_admin("[key_name(usr)] jumped to [AREACOORD(A)]") - message_admins("[key_name_admin(usr)] jumped to [AREACOORD(A)]") + log_admin("[key_name(usr)] jumped to [AREACOORD(T)]") + message_admins("[key_name_admin(usr)] jumped to [AREACOORD(T)]") . = TRUE if("teleportBack") //After teleporting to centcom, this button allows the user to teleport to the last spot they were at. var/mob/M = holder.mob @@ -587,7 +587,7 @@ force_open = FALSE, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.adm else for (var/atom/movable/O in launchList) //If we aren't cloning the objects, just go through the launchList O.forceMove(toLaunch) //and forceMove any atom/moveable into the supplypod - new /obj/effect/dp_target(A, toLaunch) //Then, create the DPTarget effect, which will eventually forceMove the temp_pod to it's location + new /obj/effect/pod_landingzone(A, toLaunch) //Then, create the DPTarget effect, which will eventually forceMove the temp_pod to it's location if (launchClone) launchCounter++ //We only need to increment launchCounter if we are cloning objects. //If we aren't cloning objects, taking and removing the first item each time from the acceptableTurfs list will inherently iterate through the list in order diff --git a/code/modules/cargo/expressconsole.dm b/code/modules/cargo/expressconsole.dm index ab8ebeb7a95..549b61dfe19 100644 --- a/code/modules/cargo/expressconsole.dm +++ b/code/modules/cargo/expressconsole.dm @@ -192,7 +192,7 @@ LZ = pick(empty_turfs) if (SO.pack.cost <= points_to_check && LZ)//we need to call the cost check again because of the CHECK_TICK call D.adjust_money(-SO.pack.cost) - new /obj/effect/dp_target(LZ, podType, SO) + new /obj/effect/pod_landingzone(LZ, podType, SO) . = TRUE update_icon() else @@ -210,7 +210,7 @@ for(var/i in 1 to MAX_EMAG_ROCKETS) var/LZ = pick(empty_turfs) LAZYREMOVE(empty_turfs, LZ) - new /obj/effect/dp_target(LZ, podType, SO) + new /obj/effect/pod_landingzone(LZ, podType, SO) . = TRUE update_icon() CHECK_TICK diff --git a/code/modules/cargo/gondolapod.dm b/code/modules/cargo/gondolapod.dm index 1216c55fca6..81c1795418b 100644 --- a/code/modules/cargo/gondolapod.dm +++ b/code/modules/cargo/gondolapod.dm @@ -10,9 +10,9 @@ response_harm_simple = "kick" faction = list("gondola") turns_per_move = 10 - icon = 'icons/mob/gondolapod.dmi' - icon_state = "gondolapod" - icon_living = "gondolapod" + icon = 'icons/obj/supplypods.dmi' + icon_state = "pod_gondola" + icon_living = "pod_gondola" pixel_x = -16//2x2 sprite pixel_y = -5 layer = TABLE_LAYER//so that deliveries dont appear underneath it @@ -30,13 +30,13 @@ /mob/living/simple_animal/pet/gondola/gondolapod/Initialize(mapload, pod) linked_pod = pod name = linked_pod.name + desc = linked_pod.desc . = ..() -/mob/living/simple_animal/pet/gondola/gondolapod/update_icon_state() +/mob/living/simple_animal/pet/gondola/gondolapod/update_overlays() + . = ..() if(opened) - icon_state = "gondolapod_open" - else - icon_state = "gondolapod" + . += "[icon_state]_open" /mob/living/simple_animal/pet/gondola/gondolapod/verb/deliver() set name = "Release Contents" @@ -61,12 +61,12 @@ else to_chat(src, "A closer look inside yourself reveals... nothing.") -/mob/living/simple_animal/pet/gondola/gondolapod/proc/setOpened() +/mob/living/simple_animal/pet/gondola/gondolapod/setOpened() opened = TRUE update_icon() - addtimer(CALLBACK(src, .proc/setClosed), 50) + addtimer(CALLBACK(src, /atom/.proc/setClosed), 50) -/mob/living/simple_animal/pet/gondola/gondolapod/proc/setClosed() +/mob/living/simple_animal/pet/gondola/gondolapod/setClosed() opened = FALSE update_icon() diff --git a/code/modules/cargo/supplypod.dm b/code/modules/cargo/supplypod.dm index be02785d09d..6e1b76157da 100644 --- a/code/modules/cargo/supplypod.dm +++ b/code/modules/cargo/supplypod.dm @@ -1,13 +1,14 @@ -//The "BDPtarget" temp visual is created by anything that "launches" a supplypod. It makes two things: a falling droppod animation, and the droppod itself. +#define SUPPLYPOD_X_OFFSET -16 + +//The "pod_landingzone" temp visual is created by anything that "launches" a supplypod. This is what animates the pod and makes the pod forcemove to the station. //------------------------------------SUPPLY POD-------------------------------------// /obj/structure/closet/supplypod name = "supply pod" //Names and descriptions are normally created with the setStyle() proc during initialization, but we have these default values here as a failsafe desc = "A Nanotrasen supply drop pod." icon = 'icons/obj/supplypods.dmi' - icon_state = "supplypod" - pixel_x = -16 //2x2 sprite - pixel_y = -5 - layer = TABLE_LAYER //So that the crate inside doesn't appear underneath + icon_state = "pod" //This is a common base sprite shared by a number of pods + pixel_x = SUPPLYPOD_X_OFFSET //2x2 sprite + layer = BELOW_OBJ_LAYER //So that the crate inside doesn't appear underneath allow_objects = TRUE allow_dense = TRUE delivery_icon = null @@ -16,6 +17,9 @@ anchored = TRUE //So it cant slide around after landing anchorable = FALSE flags_1 = PREVENT_CONTENTS_EXPLOSION_1 + appearance_flags = KEEP_TOGETHER | PIXEL_SCALE + density = FALSE + //*****NOTE*****: Many of these comments are similarly described in centcom_podlauncher.dm. If you change them here, please consider doing so in the centcom podlauncher code as well! var/adminNamed = FALSE //Determines whether or not the pod has been named by an admin. If true, the pod's name will not get overridden when the style of the pod changes (changing the style of the pod normally also changes the name+desc) var/bluespace = FALSE //If true, the pod deletes (in a shower of sparks) after landing @@ -43,7 +47,13 @@ var/bay //Used specifically for the centcom_podlauncher datum. Holds the current bay the user is launching objects from. Bays are specific rooms on the centcom map. var/list/explosionSize = list(0,0,2,3) var/stay_after_drop = FALSE - var/specialised = TRUE // It's not a general use pod for cargo/admin use + var/specialised = FALSE // It's not a general use pod for cargo/admin use + var/rubble_type //Rubble effect associated with this supplypod + var/decal = "default" //What kind of extra decals we add to the pod to make it look nice + var/door = "pod_door" + var/fin_mask = "topfin" + var/obj/effect/supplypod_rubble/rubble + var/obj/effect/engineglow/glow_effect var/effectShrapnel = FALSE var/shrapnel_type = /obj/projectile/bullet/shrapnel var/shrapnel_magnitude = 3 @@ -56,7 +66,7 @@ /obj/structure/closet/supplypod/extractionpod name = "Syndicate Extraction Pod" - desc = "A specalised, blood-red styled pod for extracting high-value targets out of active mission areas." + desc = "A specalised, blood-red styled pod for extracting high-value targets out of active mission areas. Targets must be manually stuffed inside the pod for proper delivery." specialised = TRUE style = STYLE_SYNDICATE bluespace = TRUE @@ -70,39 +80,72 @@ landingDelay = 20 //Very speedy! resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF - -/obj/structure/closet/supplypod/proc/specialisedPod() - return 1 - -/obj/structure/closet/supplypod/extractionpod/specialisedPod(atom/movable/holder) - holder.forceMove(pick(GLOB.holdingfacility)) // land in ninja jail - open_pod(holder, forced = TRUE) - /obj/structure/closet/supplypod/Initialize() . = ..() setStyle(style, TRUE) //Upon initialization, give the supplypod an iconstate, name, and description based on the "style" variable. This system is important for the centcom_podlauncher to function correctly -/obj/structure/closet/supplypod/update_overlays() - . = ..() - if (style == STYLE_SEETHROUGH || style == STYLE_INVISIBLE) //If we're invisible, we dont bother adding any overlays - return - else - if (opened) - . += "[icon_state]_open" - else - . += "[icon_state]_door" - -/obj/structure/closet/supplypod/proc/setStyle(chosenStyle, duringInit = FALSE) //Used to give the sprite an icon state, name, and description +/obj/structure/closet/supplypod/proc/setStyle(chosenStyle, duringInit = FALSE) //Used to give the sprite an icon state, name, and description. Should only be called once if (!duringInit && style == chosenStyle) //Check if the input style is already the same as the pod's style. This happens in centcom_podlauncher, and as such we set the style to STYLE_CENTCOM. setStyle(STYLE_CENTCOM) //We make sure to not check this during initialize() so the standard supplypod works correctly. return style = chosenStyle - icon_state = POD_STYLES[chosenStyle][POD_ICON_STATE] //POD_STYLES is a 2D array we treat as a dictionary. The style represents the verticle index, with the icon state, name, and desc being stored in the horizontal indexes of the 2D array. + var/base = POD_STYLES[chosenStyle][POD_BASE] //POD_STYLES is a 2D array we treat as a dictionary. The style represents the verticle index, with the icon state, name, and desc being stored in the horizontal indexes of the 2D array. + icon_state = base + decal = POD_STYLES[chosenStyle][POD_DECAL] + rubble_type = POD_STYLES[chosenStyle][POD_RUBBLE_TYPE] if (!adminNamed && !specialised) //We dont want to name it ourselves if it has been specifically named by an admin using the centcom_podlauncher datum name = POD_STYLES[chosenStyle][POD_NAME] desc = POD_STYLES[chosenStyle][POD_DESC] + door = "[base]_door" update_icon() +/obj/structure/closet/supplypod/proc/SetReverseIcon() + fin_mask = "bottomfin" + icon_state = initial(icon_state) + "_reverse" + pixel_x = initial(pixel_x) + transform = matrix() + update_icon() + +/obj/structure/closet/supplypod/closet_update_overlays(list/new_overlays) + return + +/obj/structure/closet/supplypod/update_overlays() + . = ..() + if (style == STYLE_INVISIBLE) + return + if (rubble) + . += rubble.getForeground(src) + if (style == STYLE_SEETHROUGH) + for (var/atom/A in contents) + var/mutable_appearance/itemIcon = new(A) + itemIcon.transform = matrix().Translate(-1 * SUPPLYPOD_X_OFFSET, 0) + . += itemIcon + return + + if (opened) //We're opened means all we have to worry about is masking a decal if we have one + if (!decal) //We don't have a decal to mask + return + var/icon/masked_decal = new(icon, decal) //The decal we want to apply + var/icon/door_masker = new(icon, door) //The door shape we want to 'cut out' of the decal + door_masker.MapColors(0,0,0,1, 0,0,0,1, 0,0,0,1, 1,1,1,0, 0,0,0,1) + door_masker.SwapColor("#ffffffff", null) + door_masker.Blend("#000000", ICON_SUBTRACT) + masked_decal.Blend(door_masker, ICON_ADD) + . += masked_decal + else //If we're closed + if (POD_STYLES[style][POD_SHAPE] != POD_SHAPE_NORML) //If we're not a normal pod shape (aka, if we don't have fins), just add the door without masking + . += door + else + var/icon/masked_door = new(icon, door) //The door we want to apply + var/icon/fin_masker = new(icon, "mask_[fin_mask]") //The fin shape we want to 'cut out' of the door + fin_masker.MapColors(0,0,0,1, 0,0,0,1, 0,0,0,1, 1,1,1,0, 0,0,0,1) + fin_masker.SwapColor("#ffffffff", null) + fin_masker.Blend("#000000", ICON_SUBTRACT) + masked_door.Blend(fin_masker, ICON_ADD) + . += masked_door + if (decal) + . += decal + /obj/structure/closet/supplypod/tool_interact(obj/item/W, mob/user) if(bluespace) //We dont want to worry about interacting with bluespace pods, as they are due to delete themselves soon anyways. return FALSE @@ -118,38 +161,29 @@ /obj/structure/closet/supplypod/toggle(mob/living/user) return -/obj/structure/closet/supplypod/open(mob/living/user, force = TRUE) //Supplypods shouldn't be able to be manually opened under any circumstances +/obj/structure/closet/supplypod/open(mob/living/user, force = TRUE) return -/obj/structure/closet/supplypod/proc/handleReturningClose(atom/movable/holder, returntobay) - opened = FALSE - INVOKE_ASYNC(holder, .proc/setClosed) //Use the INVOKE_ASYNC proc to call setClosed() on whatever the holder may be, without giving the atom/movable base class a setClosed() proc definition - for (var/atom/movable/O in get_turf(holder)) - if ((ismob(O) && !isliving(O)) || (is_type_in_typecache(O, GLOB.blacklisted_cargo_types) && !isliving(O))) //We dont want to take ghosts with us, and we don't want blacklisted items going, but we allow mobs. - continue - O.forceMove(holder) //Put objects inside before we close - var/obj/effect/temp_visual/risingPod = new /obj/effect/dp_fall(get_turf(holder), src) //Make a nice animation of flying back up - risingPod.pixel_z = 0 //The initial value of risingPod's pixel_z is 200 because it normally comes down from a high spot - animate(risingPod, pixel_z = 200, time = 10, easing = LINEAR_EASING) //Animate our rising pod - if (returntobay) - holder.forceMove(bay) //Move the pod back to centcom, where it belongs - QDEL_IN(risingPod, 10) - reversing = FALSE //Now that we're done reversing, we set this to false (otherwise we would get stuck in an infinite loop of calling the close proc at the bottom of open() ) - bluespace = TRUE //Make it so that the pod doesn't stay in centcom forever - open_pod(holder, forced = TRUE) - else - reversing = FALSE //Now that we're done reversing, we set this to false (otherwise we would get stuck in an infinite loop of calling the close proc at the bottom of open() ) - bluespace = TRUE //Make it so that the pod doesn't stay in centcom forever +/obj/structure/closet/supplypod/extractionpod/handleReturningClose(atom/movable/holder = src) + reversing = FALSE //Now that we're done reversing, we set this to false (otherwise we would get stuck in an infinite loop of calling the close proc at the bottom of open_pod() ) + bluespace = TRUE //Make it so that the pod doesn't stay in centcom forever + audible_message("The pod hisses, closing quickly and launching itself away from the station.", "The ground vibrates.") + stay_after_drop = FALSE + holder.forceMove(pick(GLOB.holdingfacility)) // land in ninja jail + holder.pixel_z = initial(holder.pixel_z) + open_pod(holder, forced = TRUE) - QDEL_IN(risingPod, 10) - audible_message("The pod hisses, closing quickly and launching itself away from the station.", "The ground vibrates, the nearby pod launching away from the station.") +/obj/structure/closet/supplypod/proc/handleReturningClose(atom/movable/holder = src) + holder.forceMove(bay) //Move the pod back to centcom, where it belongs + holder.pixel_z = initial(holder.pixel_z) + reversing = FALSE //Now that we're done reversing, we set this to false (otherwise we would get stuck in an infinite loop of calling the close proc at the bottom of open_pod() ) + bluespace = TRUE //Make it so that the pod doesn't stay in centcom forever + open_pod(holder, forced = TRUE) - stay_after_drop = FALSE - specialisedPod(holder) // Do special actions for specialised pods - this is likely if we were already doing manual launches - -/obj/structure/closet/supplypod/proc/preOpen() //Called before the open() proc. Handles anything that occurs right as the pod lands. +/obj/structure/closet/supplypod/proc/preOpen() //Called before the open_pod() proc. Handles anything that occurs right as the pod lands. var/turf/T = get_turf(src) var/list/B = explosionSize //Mostly because B is more readable than explosionSize :p + density = TRUE //Density is originally false so the pod doesn't block anything while it's still falling through the air if (landingSound) playsound(get_turf(src), landingSound, soundVolume, FALSE, FALSE) for (var/mob/living/M in T) @@ -193,7 +227,7 @@ var/mob/living/simple_animal/pet/gondola/gondolapod/benis = new(get_turf(src), src) benis.contents |= contents //Move the contents of this supplypod into the gondolapod mob. moveToNullspace() - addtimer(CALLBACK(src, .proc/open, benis), openingDelay) //After the openingDelay passes, we use the open proc from this supplyprod while referencing the contents of the "holder", in this case the gondolapod mob + addtimer(CALLBACK(src, .proc/open_pod, benis), openingDelay) //After the openingDelay passes, we use the open proc from this supplyprod while referencing the contents of the "holder", in this case the gondolapod mob else if (style == STYLE_SEETHROUGH) open_pod(src) else @@ -204,7 +238,7 @@ return if (opened) //This is to ensure we don't open something that has already been opened return - opened = TRUE + holder.setOpened() var/turf/T = get_turf(holder) //Get the turf of whoever's contents we're talking about var/mob/M if (istype(holder, /mob)) //Allows mobs to assume the role of the holder, meaning we look at the mob's contents rather than the supplypod's contents. Typically by this point the supplypod's contents have already been moved over to the mob's contents @@ -213,9 +247,6 @@ return if (openingSound) playsound(get_turf(holder), openingSound, soundVolume, FALSE, FALSE) //Special admin sound to play - INVOKE_ASYNC(holder, .proc/setOpened) //Use the INVOKE_ASYNC proc to call setOpened() on whatever the holder may be, without giving the atom/movable base class a setOpened() proc definition - if (style == STYLE_SEETHROUGH) - update_icon() for (var/atom/movable/O in holder.contents) //Go through the contents of the holder O.forceMove(T) //move everything from the contents of the holder to the turf of the holder if (!effectQuiet && !openingSound && style != STYLE_SEETHROUGH) //If we aren't being quiet, play the default pod open sound @@ -225,12 +256,12 @@ if (style == STYLE_SEETHROUGH) depart(src) else + if (reversing) + addtimer(CALLBACK(src, .proc/SetReverseIcon), departureDelay/2) //Finish up the pod's duties after a certain amount of time if(!stay_after_drop) // Departing should be handled manually addtimer(CALLBACK(src, .proc/depart, holder), departureDelay) //Finish up the pod's duties after a certain amount of time /obj/structure/closet/supplypod/proc/depart(atom/movable/holder) - if (leavingSound) - playsound(get_turf(holder), leavingSound, soundVolume, FALSE, FALSE) if (reversing) //If we're reversing, we call the close proc. This sends the pod back up to centcom close(holder) else if (bluespace) //If we're a bluespace pod, then delete ourselves (along with our holder, if a seperate holder exists) @@ -240,74 +271,216 @@ if (holder != src) qdel(holder) -/obj/structure/closet/supplypod/centcompod/close(atom/movable/holder) //Closes the supplypod and sends it back to centcom. Should only ever be called if the "reversing" variable is true - handleReturningClose(holder, TRUE) - -/obj/structure/closet/supplypod/extractionpod/close(atom/movable/holder) //handles closing, and returns pod - deletes itself when returned - . = ..() - return - -/obj/structure/closet/supplypod/extractionpod/proc/send_up(atom/movable/holder) +/obj/structure/closet/supplypod/close(atom/movable/holder) //Closes the supplypod and sends it back to centcom. Should only ever be called if the "reversing" variable is true if (!holder) - holder = src + return + message_admins(holder) + take_contents(holder) + playsound(holder, close_sound, close_sound_volume, TRUE, -3) + holder.setClosed() + addtimer(CALLBACK(src, .proc/preReturn, holder), 10) //Finish up the pod's duties after a certain amount of time +/obj/structure/closet/supplypod/take_contents(atom/movable/holder) + var/atom/L = holder.drop_location() + for(var/atom/movable/AM in L) + if(AM != src && !insert(AM, holder)) // Can't insert that + continue + +/obj/structure/closet/supplypod/insert(atom/movable/AM, atom/movable/holder) + if(insertion_allowed(AM)) + AM.forceMove(holder) + return TRUE + else + return FALSE + +/obj/structure/closet/supplypod/insertion_allowed(atom/movable/AM) + if(ismob(AM)) + if(!isliving(AM)) //let's not put ghosts or camera mobs inside closets... + return FALSE + var/mob/living/L = AM + if(L.anchored || L.incorporeal_move) + return FALSE + L.stop_pulling() + + else if(isobj(AM)) + if((!allow_dense && AM.density) || AM.anchored || AM.has_buckled_mobs()) + return FALSE + else if(isitem(AM) && !HAS_TRAIT(AM, TRAIT_NODROP)) + return TRUE + else if(!allow_objects && !istype(AM, /obj/effect/dummy/chameleon)) + return FALSE + else + return FALSE + + return TRUE + +/obj/structure/closet/supplypod/proc/preReturn(atom/movable/holder) if (leavingSound) playsound(get_turf(holder), leavingSound, soundVolume, FALSE, FALSE) + deleteRubble() + animate(holder, alpha = 0, time = 8, easing = QUAD_EASING|EASE_IN, flags = ANIMATION_PARALLEL) + animate(holder, pixel_z = 400, time = 10, easing = QUAD_EASING|EASE_IN, flags = ANIMATION_PARALLEL) //Animate our rising pod + + addtimer(CALLBACK(src, .proc/handleReturningClose, holder), 15) //Finish up the pod's duties after a certain amount of time - handleReturningClose(holder, FALSE) - -/obj/structure/closet/supplypod/proc/setOpened() //Proc exists here, as well as in any atom that can assume the role of a "holder" of a supplypod. Check the open() proc for more details +/obj/structure/closet/supplypod/setOpened() //Proc exists here, as well as in any atom that can assume the role of a "holder" of a supplypod. Check the open_pod() proc for more details + opened = TRUE + density = FALSE update_icon() -/obj/structure/closet/supplypod/proc/setClosed() //Ditto +/obj/structure/closet/supplypod/extractionpod/setOpened() + opened = TRUE + density = TRUE update_icon() +/obj/structure/closet/supplypod/setClosed() //Ditto + opened = FALSE + density = TRUE + update_icon() + +/obj/structure/closet/supplypod/proc/tryMakeRubble(turf/T) //Ditto + if (rubble_type == RUBBLE_NONE) + return + if (rubble) + return + if (effectMissile) + return + if (isspaceturf(T) || isclosedturf(T)) + return + rubble = new /obj/effect/supplypod_rubble(T) + rubble.setStyle(rubble_type, src) + update_icon() + +/obj/structure/closet/supplypod/Moved() + if (rubble) + deleteRubble() + return ..() + +/obj/structure/closet/supplypod/proc/deleteRubble() + rubble?.fadeAway() + rubble = null + update_icon() + +/obj/structure/closet/supplypod/proc/addGlow() + if (POD_STYLES[style][POD_SHAPE] != POD_SHAPE_NORML) + return + glow_effect = new(src) + glow_effect.icon_state = "pod_glow_" + POD_STYLES[style][POD_GLOW] + vis_contents += glow_effect + glow_effect.layer = GASFIRE_LAYER + +/obj/structure/closet/supplypod/proc/endGlow() + if(!glow_effect) + return + glow_effect.layer = LOW_ITEM_LAYER + glow_effect.fadeAway(openingDelay) + message_admins("HUU!") + /obj/structure/closet/supplypod/Destroy() - open_pod(holder = src, broken = TRUE) //Lets dump our contents by opening up - . = ..() - -//------------------------------------FALLING SUPPLY POD-------------------------------------// -/obj/effect/dp_fall //Falling pod - name = "" - icon = 'icons/obj/supplypods.dmi' - pixel_x = -16 - pixel_y = -5 - pixel_z = 200 - desc = "Get out of the way!" - layer = FLY_LAYER//that wasn't flying, that was falling with style! - icon_state = "" - -/obj/effect/dp_fall/Initialize(dropLocation, obj/structure/closet/supplypod/pod) - if (pod.style == STYLE_SEETHROUGH) - pixel_x = -16 - pixel_y = 0 - for (var/atom/movable/O in pod.contents) - var/icon/I = getFlatIcon(O) //im so sorry - add_overlay(I) - else if (pod.style != STYLE_INVISIBLE) //Check to ensure the pod isn't invisible - icon_state = "[pod.icon_state]_falling" - name = pod.name - . = ..() + open_pod(src, broken = TRUE) //Lets dump our contents by opening up + if (rubble) + deleteRubble() + return ..() //------------------------------------TEMPORARY_VISUAL-------------------------------------// -/obj/effect/dp_target //This is the object that forceMoves the supplypod to it's location +/obj/effect/supplypod_smoke //Falling pod smoke + name = "" + icon = 'icons/obj/supplypods_32x32.dmi' + icon_state = "smoke" + desc = "" + layer = PROJECTILE_HIT_THRESHHOLD_LAYER + mouse_opacity = MOUSE_OPACITY_TRANSPARENT + alpha = 0 + +/obj/effect/engineglow //Falling pod smoke + name = "" + icon = 'icons/obj/supplypods.dmi' + icon_state = "pod_engineglow" + desc = "" + layer = GASFIRE_LAYER + mouse_opacity = MOUSE_OPACITY_TRANSPARENT + alpha = 255 + +/obj/effect/engineglow/proc/fadeAway(leaveTime) + var/duration = min(leaveTime, 25) + animate(src, alpha=0, time = duration) + message_admins("HYUU!") + QDEL_IN(src, duration + 5) + +/obj/effect/supplypod_smoke/proc/drawSelf(amount) + alpha = max(0, 255-(amount*20)) + +/obj/effect/supplypod_rubble //This is the object that forceMoves the supplypod to it's location + name = "Debris" + desc = "A small crater of rubble. Closer inspection reveals the debris to be made primarily of space-grade metal fragments. You're pretty sure that this will disperse before too long." + icon = 'icons/obj/supplypods.dmi' + layer = PROJECTILE_HIT_THRESHHOLD_LAYER // We want this to go right below the layer of supplypods and supplypod_rubble's forground. + icon_state = "rubble_bg" + anchored = TRUE + pixel_x = SUPPLYPOD_X_OFFSET + var/foreground = "rubble_fg" + var/verticle_offset = 0 + +/obj/effect/supplypod_rubble/proc/getForeground(obj/structure/closet/supplypod/pod) + var/mutable_appearance/rubble_overlay = mutable_appearance('icons/obj/supplypods.dmi', foreground) + rubble_overlay.appearance_flags = KEEP_APART|RESET_TRANSFORM + rubble_overlay.transform = matrix().Translate(SUPPLYPOD_X_OFFSET - pod.pixel_x, verticle_offset) + return rubble_overlay + +/obj/effect/supplypod_rubble/proc/fadeAway() + animate(src, alpha=0, time = 30) + QDEL_IN(src, 35) + +/obj/effect/supplypod_rubble/proc/setStyle(type, obj/structure/closet/supplypod/pod) + if (type == RUBBLE_WIDE) + icon_state += "_wide" + foreground += "_wide" + if (type == RUBBLE_THIN) + icon_state += "_thin" + foreground += "_thin" + if (pod.style == STYLE_BOX) + verticle_offset = -2 + else + verticle_offset = initial(verticle_offset) + + pixel_y = verticle_offset + +/obj/effect/supplypod_target_helper + name = "" + desc = "" + icon = 'icons/obj/supplypods_32x32.dmi' + icon_state = "LZ_Slider" + layer = PROJECTILE_HIT_THRESHHOLD_LAYER + +/obj/effect/supplypod_target_helper/Initialize(mapload, obj/structure/closet/supplypod/pod) + transform = matrix() * 1.5 + animate(src, transform = matrix()*0.01, time = pod.landingDelay+pod.fallDuration) + ..() + +/obj/effect/pod_landingzone //This is the object that forceMoves the supplypod to it's location name = "Landing Zone Indicator" desc = "A holographic projection designating the landing zone of something. It's probably best to stand back." - icon = 'icons/mob/actions/actions_items.dmi' - icon_state = "sniper_zoom" + icon = 'icons/obj/supplypods_32x32.dmi' + icon_state = "" layer = PROJECTILE_HIT_THRESHHOLD_LAYER light_range = 2 - var/obj/effect/temp_visual/fallingPod //Temporary "falling pod" that we animate + anchored = TRUE var/obj/structure/closet/supplypod/pod //The supplyPod that will be landing ontop of this target + var/obj/effect/supplypod_target_helper/helper + var/list/smoke_effects = new /list(13) /obj/effect/ex_act() return -/obj/effect/dp_target/Initialize(mapload, podParam, single_order = null) +/obj/effect/pod_landingzone/Initialize(mapload, podParam, single_order = null, clientman) . = ..() if (ispath(podParam)) //We can pass either a path for a pod (as expressconsoles do), or a reference to an instantiated pod (as the centcom_podlauncher does) podParam = new podParam() //If its just a path, instantiate it pod = podParam + if (!pod.effectStealth) + helper = new (drop_location(), pod) + icon_state = "LZ" + animate(src, transform = matrix().Turn(90), time = pod.landingDelay+pod.fallDuration) if (single_order) if (istype(single_order, /datum/supply_order)) var/datum/supply_order/SO = single_order @@ -316,12 +489,10 @@ var/atom/movable/O = single_order O.forceMove(pod) for (var/mob/living/M in pod) //If there are any mobs in the supplypod, we want to forceMove them into the target. This is so that they can see where they are about to land, AND so that they don't get sent to the nullspace error room (as the pod is currently in nullspace) - M.forceMove(src) + M.reset_perspective(src) if(pod.effectStun) //If effectStun is true, stun any mobs caught on this target until the pod gets a chance to hit them for (var/mob/living/M in get_turf(src)) - M.Stun(pod.landingDelay+10, ignore_canstun = TRUE)//you ain't goin nowhere, kid. - if (pod.effectStealth) //If effectStealth is true we want to be invisible - icon_state = "" + M.Stun(pod.landingDelay+10, ignore_canstun = TRUE)//you aint goin nowhere, kid. if (pod.fallDuration == initial(pod.fallDuration) && pod.landingDelay + pod.fallDuration < pod.fallingSoundLength) pod.fallingSoundLength = 3 //The default falling sound is a little long, so if the landing time is shorter than the default falling sound, use a special, shorter default falling sound pod.fallingSound = 'sound/weapons/mortar_whistle.ogg' @@ -332,34 +503,63 @@ addtimer(CALLBACK(src, .proc/playFallingSound), soundStartTime) addtimer(CALLBACK(src, .proc/beginLaunch, pod.effectCircle), pod.landingDelay) -/obj/effect/dp_target/proc/playFallingSound() +/obj/effect/pod_landingzone/proc/playFallingSound() playsound(src, pod.fallingSound, pod.soundVolume, TRUE, 6) -/obj/effect/dp_target/proc/beginLaunch(effectCircle) //Begin the animation for the pod falling. The effectCircle param determines whether the pod gets to come in from any descent angle - fallingPod = new /obj/effect/dp_fall(drop_location(), pod) - var/matrix/M = matrix(fallingPod.transform) //Create a new matrix that we can rotate +/obj/effect/pod_landingzone/proc/beginLaunch(effectCircle) //Begin the animation for the pod falling. The effectCircle param determines whether the pod gets to come in from any descent angle + pod.addGlow() + pod.update_icon() + if (pod.style != STYLE_INVISIBLE) + pod.add_filter("motionblur",1,list("type"="motion_blur", "x"=0, "y"=3)) + pod.forceMove(drop_location()) + for (var/mob/living/M in pod) //Remember earlier (initialization) when we moved mobs into the pod_landingzone so they wouldnt get lost in nullspace? Time to get them out + M.reset_perspective(null) var/angle = effectCircle ? rand(0,360) : rand(70,110) //The angle that we can come in from - fallingPod.pixel_x = cos(angle)*400 //Use some ADVANCED MATHEMATICS to set the animated pod's position to somewhere on the edge of a circle with the center being the target - fallingPod.pixel_z = sin(angle)*400 - var/rotation = Get_Pixel_Angle(fallingPod.pixel_z, fallingPod.pixel_x) //CUSTOM HOMEBREWED proc that is just arctan with extra steps - M.Turn(rotation) //Turn our matrix accordingly - fallingPod.transform = M //Transform the animated pod according to the matrix - M = matrix(pod.transform) //Make another matrix based on the pod - M.Turn(rotation) //Turn the matrix - pod.transform = M //Turn the actual pod (Won't be visible until endLaunch() proc tho) - animate(fallingPod, pixel_z = 0, pixel_x = -16, time = pod.fallDuration, , easing = LINEAR_EASING) //Make the pod fall! At an angle! + pod.pixel_x = cos(angle)*32*length(smoke_effects) //Use some ADVANCED MATHEMATICS to set the animated pod's position to somewhere on the edge of a circle with the center being the target + pod.pixel_z = sin(angle)*32*length(smoke_effects) + var/rotation = Get_Pixel_Angle(pod.pixel_z, pod.pixel_x) //CUSTOM HOMEBREWED proc that is just arctan with extra steps + setupSmoke(rotation) + pod.transform = matrix().Turn(rotation) + pod.layer = FLY_LAYER + if (pod.style != STYLE_INVISIBLE) + animate(pod.get_filter("motionblur"), y = 0, time = pod.fallDuration, flags = ANIMATION_PARALLEL) + animate(pod, pixel_z = -1 * abs(sin(rotation))*4, pixel_x = SUPPLYPOD_X_OFFSET + (sin(rotation) * 20), time = pod.fallDuration, easing = LINEAR_EASING, flags = ANIMATION_PARALLEL) //Make the pod fall! At an angle! addtimer(CALLBACK(src, .proc/endLaunch), pod.fallDuration, TIMER_CLIENT_TIME) //Go onto the last step after a very short falling animation -/obj/effect/dp_target/proc/endLaunch() - pod.update_icon() - pod.forceMove(drop_location()) //The fallingPod animation is over, now's a good time to forceMove the actual pod into position +/obj/effect/pod_landingzone/proc/setupSmoke(rotation) + if (pod.style == STYLE_INVISIBLE || pod.style == STYLE_SEETHROUGH) + return + for ( var/i in 1 to length(smoke_effects)) + var/obj/effect/supplypod_smoke/S = new (drop_location()) + if (i == 1) + S.layer = FLY_LAYER + S.icon_state = "smoke_start" + S.transform = matrix().Turn(rotation) + smoke_effects[i] = S + S.pixel_x = sin(rotation)*32 * i + S.pixel_y = abs(cos(rotation))*32 * i + S.filters += filter(type = "blur", size = 4) + var/time = (pod.fallDuration / length(smoke_effects))*(length(smoke_effects)-i) + addtimer(CALLBACK(S, /obj/effect/supplypod_smoke/.proc/drawSelf, i), time, TIMER_CLIENT_TIME) //Go onto the last step after a very short falling animation + QDEL_IN(S, pod.fallDuration + 35) + +/obj/effect/pod_landingzone/proc/drawSmoke() + if (pod.style == STYLE_INVISIBLE || pod.style == STYLE_SEETHROUGH) + return + for (var/obj/effect/supplypod_smoke/S in smoke_effects) + animate(S, alpha = 0, time = 20, flags = ANIMATION_PARALLEL) + animate(S.filters[1], size = 6, time = 15, easing = CUBIC_EASING|EASE_OUT, flags = ANIMATION_PARALLEL) + +/obj/effect/pod_landingzone/proc/endLaunch() + pod.tryMakeRubble(drop_location()) + pod.layer = initial(pod.layer) + pod.endGlow() + QDEL_NULL(helper) pod.AddComponent(/datum/component/pellet_cloud, projectile_type=pod.shrapnel_type, magnitude=pod.shrapnel_magnitude) if(pod.effectShrapnel) SEND_SIGNAL(pod, COMSIG_SUPPLYPOD_LANDED) - QDEL_NULL(fallingPod) //Delete the falling pod effect, because at this point its animation is over. We dont use temp_visual because we want to manually delete it as soon as the pod appears - for (var/mob/living/M in src) //Remember earlier (initialization) when we moved mobs into the DPTarget so they wouldnt get lost in nullspace? Time to get them out - M.forceMove(pod) pod.preOpen() //Begin supplypod open procedures. Here effects like explosions, damage, and other dangerous (and potentially admin-caused, if the centcom_podlauncher datum was used) memes will take place + drawSmoke() qdel(src) //The target's purpose is complete. It can rest easy now //------------------------------------UPGRADES-------------------------------------// @@ -370,3 +570,5 @@ icon_state = "cargodisk" inhand_icon_state = "card-id" w_class = WEIGHT_CLASS_SMALL + +#undef SUPPLYPOD_X_OFFSET diff --git a/code/modules/events/stray_cargo.dm b/code/modules/events/stray_cargo.dm index f9f0f359110..e8e6b90d13a 100644 --- a/code/modules/events/stray_cargo.dm +++ b/code/modules/events/stray_cargo.dm @@ -55,7 +55,7 @@ crate.locked = FALSE //Unlock secure crates crate.update_icon() var/obj/structure/closet/supplypod/pod = make_pod() - new /obj/effect/dp_target(LZ, pod, crate) + new /obj/effect/pod_landingzone(LZ, pod, crate) ///Handles the creation of the pod, in case it needs to be modified beforehand /datum/round_event/stray_cargo/proc/make_pod() diff --git a/icons/mob/gondolapod.dmi b/icons/mob/gondolapod.dmi index 4c56c1bb556223654a45ee2e242960336b18d006..36a4825bcebf18adfca5b1d2022bf4d8ad9cb953 100644 GIT binary patch delta 1072 zcmV-01kd}s3a1H>Bmp6jB_bHWz`!8-YyAKK00DGTPE!Ct=GbNc005JbWk`RiAU`EO zJwGocKPOR%i!&v&s2HS`i!-e#F*g;&HY7o<5l}QWC$SQwO~;UnGdI61H8(Y{1gA0a zDf#(D2(t`P>{V89^>YDx3IJ|6KvtMH&qM$K1C>ccK~#90?VLSs+%Ob`Nx%mX4K*n( z_yjqDwP52mfiI9I7YK5Nqz!-IW^Wt*N~A<;n;lZX&J?!1-}reft-a6=K_MuLq9}@@ zP9>>~I&$?q8L6QgpS)fw4m6ij)JqpGo)^PZjELrIz;Z-1?;jvJL&bB1<_zErU#IY) zG62cDo&opIaZLE@5Fc&A#W0$PK6unAucHlxi~8vx?&cyNYAgSkZ603^2ty)%s204R5;WQb2w zG-m*JJZ}tA=L3+O0T_S*44MmuIT?WE-Z10_wH*NDa&%wh2KSh1!riN7*)TWZrwE`* zhH)JL<-wpbgW3(i@nC<@?+LfZn*lf;4f=C6d=>`4xHY&7^nQ)`+`*6xfbzw#(QW{W zJA>Rglt>34xibiL{Y>%o-;fPJa&M68%WAn^mAjdnn{e^`>RuI;u>>i9Kdt?3JeW&m z4E|L?MY-=Ymu)gZ(cHdqk=!IDlI~t|K=Tl*Wbk3pyg(?5q9}igqI!z!2fVm`fa?d^ z2XN2JgKihieYJXEKy%-s)(vPLO3Zq{kLHP?@*9A8tX31A_xotRRI5{^-$(PMSiDsF zeKcR2%}T$I=1H}Bu@^||+5iwQmP%>AkLG2=cw@jFdDU=KyWNvBz!{p;?g2{GYB~VR zy9bar3~26~&7*$-V19}K>ZspG^I)j=`)FP*mzVw@%nuC5?f1R-c>{3$z~ihRu+^$P zKec`z%zd?*T_CSuK=V-D*86=lkJW9p-$(PPqkg{y7YF@+BQ9$FemlNV?Du=|oBckT zQxru}6h-wB*AIAc{Q%bwaQ}f|*!;Pm`Oc7ZyG{7E-%o$L-6njSFs)YMytF?+YC!Yi z{s39G3+BcB0mjvU=HYCNdB2}Dn`pio(tf`*0L0@Wlk|PO-2fzC7MY}mO{)dvAypLYKNe;beH&QR$0mu?r$ouSb0%h>Irxi{?l{iWMQ^XmQpQD9!( zAD|Jh??2Fp*Y_W2#3_oRD2k#eilQirqA2QAdfOc|me%gmlkOKFhyfUY0T_S*7@${x qrx>8q&llkwy!HF}6h%=FssGBwf6SM_>X`rl000O{MNUMnLSTZ$K@3{} delta 1221 zcmV;$1Umbt3A+lABmpOpB_bYkOF6SnO@>%Q!wBG#00001bW%=J06^y0W&i*Hk&$0W zeN1iHkEOv#1!PlZ!L0C^0t`#5TmHG(Nu|HID@K*p(UqMN@MUD?z$+ z47oUS^UG3mQ}ar|rYI}8`niC81OVHhKRQFTTUG!71SLsCK~#90?VUev)Ib=;?HhO^ zHCPSx2MDsbI9udvA z4ggJ4nT5esLb&};^VI-8Gz*8P@504<#;siX09*7 ztBWi5u3)Ss$oTu|!QaM%d2Ou0zblw1_xtS2*4dzFK7Mc^Zjyv*cQ4tXe|d6h%?gmg4?^7xxFaKfwKg_Z)!x15w-`;Qj#j2R7Otc#QZ_Z@*DI8gB2O9KLMoBr`Ae|$DX{`^-T0OC`>zceiSe-xkl{nx<%z3BZvh>L#zHS$dV zkKwZ4e;xIb@Bbm3`vcq`;Qj#j2e>~#Q4~c{6eY#|0WapW(tN`B9s+3pG+Pe9^34O(XAEfWo6S=L zz^Qcq(ehFTk=>H4xD%bCq;|s-pe=B~r-$!$b zq9}@@sBOgk0Wa~@RrWkOl4!nt-nfHa`FdOkqX z?Si>_K0rGg&^%m?HtqLCvx(-jLH7IF01%H$Ch~K<)c_=)f0j(7VbN+qc?fg5`$cjF z*h78K0GgpcGvMv`jsuu7Ja6dB@Je5?kOQ#X8`k}Pn+yQ*-2X2yHJ@PacRWtF-%!4b z0NfvV=lcWou-o$o{B1m%JA=~iPu(t>JA=~i*Rk70b8lGp`%|}z=GpTBqQE?RK0qO! jKYySQ&!0a~7l{7{Ly%j9$`t@~00000NkvXXu0mjfZ;MyB diff --git a/icons/obj/supplypods.dmi b/icons/obj/supplypods.dmi index d21da6d53ae1737430baf5df5448ee5c60fa0b06..4dfc996f45bc19f2c6ea128de8be2cd9d353dc44 100644 GIT binary patch literal 26572 zcmd43cRZEh|37{oduEjp*(6FfS%*Xlk%pC(jBJt3IYubEgeb>|lCt+Y$F6L$_X=6Z zK5;m|>-2elzMt>+`}qC;`#m1XIrq5k>v~ZpYagK{_{SxQ!-d%s=LNq6nbwn!iiR|OA*YoXv zZoWtyvAlLEBzar+rTg=#|Z8ZMSA(b3a2nB_`kBxCFj1Il!qGim6ya5+*ynSUEZ}`WjPKFQ5G#bIR8E z`*<*JD~|Gjn%C;wpQjWpZy|^mx^+YSzVG|>4*`$v4^0!`Z$5}mBjTh@zcP7f9Q za0i>sBb`|^FY6Z>cdO$G9b-4O#a>IZqKL&k!)JFD50WI+xS?3<=HBSjFqOLMKpqZ~ z@Wg*#XPZvU8{_F<&EF;FmNnsD%uZ$q>#E=AVMEGLAyu-#NEMgSxJ0PL&OE`YS6t#4 zCt_j$Ex$y`XXn?o9|Bh4CH_kP=VfqTcSJZDrI>}0_x_iunv-J&9d4LF%+Q2~W-(Vy z<5Km2*1hV^sTXX7&#vZH2m24|drZw;e8dS|V7$yF;|JSNmv+XLkBz&mG&(wx0)CYk zTGp#aMY;$k$g07+yO7>VFIF%Cud-8V@$QsgF*1VR)Mt{zEq*W>_-9O^z}6IY{Ml%2YoiupyQ*b8c`|U4Q zr}QkNyH61DpE6EW?qn!NHidDwpOW5`^zCbrsB(!8ea+l<%-_-sq(pzrz|{tJdUi42 zbHH=CmvzOvemd^a;wRlBp3B;PLrGvE#eqxwWR&DENUCz{x`#*pU}bZQ29pvm1=8W{ z*C2U4$LNagz5x%&VM}$hB@WdSXf9z{N|#dUx_4*jI-)P21^_tWoLfY-fsLJMP*!2;4$0BbmRG2 zC|>&c3_(bUO_rm`Q4-QuISF`FWBTH7_=Fm0%H6bYW<};2K#&k}*S=?XqDMCRfubwh zdL%HLrf38~owQ$klz}K!Q}dBeDY7rN+Q3pXY%iQ@@AAaiC4tU(S5ztDvLTiD)sUl6o#I#Xz zFeTI7o=qW1dc-fNV9ZQd{c%;hJ$jO*nNILPS(?lEE96GSc=W^}P+3&8AmLyxQaerY z0ho9}L1KUm17u)qKsZD^e=v23utM@QH!C992-6M6{1O<3fYMSK&gk>uFPv2#hEZgfmNLU&FonQbUSWN1TDA9WJfjFIs4w5lX!X23{2h~o0~T79 zZN$%ueYezzmwp_4z!0?==s8jO9e2dJdR}!L)p0kIw~cfqJ&9Z_{BCay(3ML-Qxa>0 zSUQcwNqBqat6X}>puLwH7tI&eFG`bW#d8WZw2%7?@*?O}dZPZ0!Fg+U!@@#~#rLlc z$Oe|f<8##1Us3kS1J`zaYdXlf`>bg`WLV8JL{TJE#95@IO(b~(@2(Y!#=fjKAWUpR{3-D*FTn`OnlFqy;RrN$sH*1hZ7Z2-)MC<$? z{I79RVL|^Hr+2tqOte67j)9I5J+pTFgD&LNPf;=oT+{kmbaC++gr6yGm{eP?@{vV5*|Q* zUU}BggizjD>nyZ*YlrstCP*7wE|mq@mm!1})sf+dr{RxBb_o!l{x~)*PI%p_8dY^iAQV}}62-aw|JZWA(AvhT_yI~Bpgj`(ratN6>zs#|@Mmo00k z@|{k@Uk`Nt8&yBhPS7H~j7s{q@Yw$`sDM`M8gbA^kKXs?J?B)G3}hduawVrFdBP3* zYYJzP4R%4X1+!9H8a`tpihOyb&i$nQ`P3_T`Ui&deGIE+RgWD4>p!fB<*JGDG2m^Q z!l7c1g^OQeEr*1V5JH-Qo*Q{#!p8<^j^X`)X6(&G3H;q#lkae!K-48h)iXQ_A|EJk zlyHKDL&Z!4^)O0a%*K;1hy3&Kd0idpX_ z5JU{OJPmQJK=*Fl-57jhiP>9Z6ZEj3M^zv$s*Yn-f$`j4hTUBUL4_KUn}_>PiiE$Y zbPG+a=>aK8Vxz4RQ4K4$@@`2&>vwsM4|yun|4MKB<{yh&oPI6V5_tjJ;>;%GS;VI8>A?3bJ$TWR_bn%NB z8<&C?8Buon50n2b0YshmQ(dT+Xt8r-Q=0p{m^Yt#ZfQZND!AW{u^=7dEHd@f`_vf* za+2I!l19Vn4jzPf#o^CAwvxHBZ|pL@NWOtNgfIetDZspK#+03o! zNC2T|d~7M%YXD(a(#G|*UM)Wl*NXGy`Z@yf67xTn>wicXj|7Ri6V>eQ0ShqKC~FN^ zRN3s_>u8b7mlAIz7A2)LJ}>xXG4LxNx3}v>$EBb)=%r9$wHiQq=0Z~+&NtmlQ&pjF z%-Gil0j?P>af$~stuP3pMry8J1PkX>op{A0z*E)fS;XK+GxyQHL;IGjhN$b;csKU;+^xQ)! z=l4zV2*PQLp&7EVV`~FDw53JI`AQ$I-@(!nIkr?Y5~=*cy?OM;rPwI7rF((Pl=GSFU z7vX?)2|&O6p-R`c!>2m8#mB#)HFv**^lT``&Rpf8ra=AV5QmR z>o80^yE_GX*zMOg;$Vz*ZpH5p;HL6TpRJnr?ErM z!kssYJsm`V2?^Oi#g=DgQ8u|@Fl|_<77*oIO}mBVSeQL~5S~sp2AT9ihd$-7+pbiX ztc9PSXdfJ;$dhLfflKTHof-|J4A{i4m!&x2ZgdJQD+~6Yt;quMOEAARR+}pdpgymeUF11Hy z%ysnk9+}6^yPYXK{rU4Y(e6<}m6{5O-Z<>mz~N;nEp7=5 zKQmzd5j=4Cnvd)&^tI}v=xCj>ofp`kO(u{NQU34uR0ZF)$Lz5Ym4=% z`sv)5ebo9L&aAx>X=bf6EuB}<;W?2E0*3|}1}Kk_ipZ< z=lOQZ@0$*f6DuiJ$K%_%kwRJ5a>sAJ^wLx$imh{}?$V}H>o3#aesV6%|2oC~`OU)% zLs2{R3-cjwwx!?4qKc3Xf+Qp)#LDM)rAvvJJR8)P;}l*#R0b+4XPmPb*geq?Hh8Z} zZDJWdo z*xRJ8PIQFC%=m-NBKH?h*v%W|RbzYuBKM8T2xDsa-JT1?NB6n)?P52W&&%PhC z_tbl#iC0!;WGytYtl+J03mVuaipt`6l%>oAQOyw+q1zl1#Oa)|Ajaz$@+SZ$!TkYC zw@J%z(<^Ex{p#|=l7*{&s)%V3WyLuTvbMd0@oi~b?uGPh?TURKkU-X$U;aU*Yr3Oo zdbs{FF36mvPz-*JTOI{>J8NNnDE_!ZIp^o{Ow@usR!1pNUGO1+k3YL%qV07Iru^yP z?3z>iy(zP5ub};F9j*iuX}ixuSK|Anr0Cjwg6}Tv_Kq+ZaCXx{Pl8^6q+ym|^5>C4 zqPb!=$h*mK=}fb3$#H)CbzbFv%`J30B*0aa0p3l*UT^M{Yhr?#0{i3?srnrZDPQbHT*uSIGD;TVm=2!CTK*5w!4l^i z{P#685xgWPq`5X3E0j4d!6;c9iibt!Yl*4_KQNpRqW%n%7jb=i-E+ccO$REmQ5E0Q z1gpBa#QM~PaR)mn(CfvgFR5A9Rq8Kl{GU%7%;mIUXNO9{XX4(_<@4mNoNGg8_yV#NCX? zBSC$c`e0YuZU`R(KE94ddF*{>7}&SY+Q^lQX%CqpyA@=A-O{Co8d@o_Ve?;S6gXM( zfDI+5%e9Bx?daVy>Kip_U^-Ma_FKP5w|IaOhRm{<0KNd(%GfIZq6|7twUf4-O z%FhxlC$7Igw_R}lFf!d^&FjVdz7`$XVq&y1Cmb#HGk)gx(Omp_DMoits)TGTXKI;g z$&j!=OGGTyvI}wj9yC-ODU4(&3^L00ZD1mW`7QDjOfqq(-lD)=6e4}?jitmB-r2Yd zCL|NR>amqE4rCIG{P4V4L1+Sk&xp@`EJTY-Mxyo=u}e! zYIrldyUugsDE;o!lI)$wm5(8=oPo!(j~Fwh`;*~DGdPg!OFUuBG-a2h>PvLk-JC>H zUNXNxnXuXIsKvFEGfi6geYi9esw{g%Z*(~FzopCWYnq>GaT$3=5HCf zh{5+`pCp>mNsvM$C0SHbLpInlv*5ffZT#DD2@(M3%~fBYM(5mDyEzYne1H=1Vam>y z=-Eh~xUokq?t-VcUD!j2wM)LSaGDzCS^= z_P-6I!a8g8FmJt|ikhjzZ~lq7(61q&Ujgd2RHa{zPj}FgO zso9l~AX7!J$*6LldpjOTaH?g4cwcd|1X_$HS#iTE181EMn$UVzuuE)~9EGf^@L*va z`5hYAcCk=sUAR)j1wstRt-N}Ho9~k)ZS#?Z<+-;%DO0haf&X|-hGO#;O1+7^!b28G zHs53JW3y5BG@{@|Z~NBT%x&gCx#Qk4g{y8qlc~hdF6P)gN#g6Uth3wN+wC=knLBqH zNNmRkOPDf)sl5F+6-H)el6%oJR{P@>5tFnKqb7=4_3HXtLeHEd0YtY-&6Q z3D8m|)?pYzn?0;KTk%3HKrDZaGaE~jBB|k5j~ZPjdnt?R`SWNF7@Qx8e@i9;wDq8@ zNvDqkiEO{U`03H?mUTM>KQ+oje-049yyaB`2$Fb(El?z3eEkY#`c%A~_@(+7Q2Y9- zEqGw$gwj@9Z8%j z1GP8eQciX@39te400^uh9MpMWfX4vWR`uMj)426RGb!m&IR~+j`N9DkKSvTOa@yz; z_>ppp-rc)vX5q_7W_~IVu6EG!mAWZ<*xm12%$wQ_MjgE~Ig7HsZd0g)qaHJF3(AJ< z`&8f87ZwJN(23M*liW?e)mEDVNZ()BJ*@sC>mX{#`jxTtV5@v$ijnaJ{J9Q-ZuyNJt13^_fKOdU?#4BqF}RA}o&|;G)Jg zz9mze5lE5KljDOo|9$rL2=7Jx4$4W&S8hu@m)x2978HU`m=2=$^tbr5ugHYG! z8k_MF4iC2IGQVcmj+~@gfjjWNCirAIo{WS7lYSa8GkRT3i;m>Uzk72jYX5WZb{MMI zEXRJnH(KfH??NtV8#Pz57=OF7^OW!`Q$j^%b@lzUIr&P)1SX(DxQWX!_xzB5mu+cb z-pkLF?*cpUDJPr#DJe~{sHFxVJjvy5`3I|Y!W2jVr#(BA&x?vbOE-$lCDk7QHB0(H z)o|MRA5$}_{HlQxy!y7bxqP7{CJCs!1srlor3FSpJ_wsvIPE4Ry|E%z5Y!~L_l_#p z|2+7gTL3=&KewC(K-uD^n=TX2CH7*N4rRtipRGnYGn^>^-~Ww-Z9*!u>f}1=@PFZi zoV#l~!X7Uc)qg=L6$hA@^;8ZH(SD8pJuZ9PzGu>1sjLTZ_AZx+FZ`!kuV7YA!{}H} zGEC!tuD(+>I7Iz5UV}Tf>Tg5-#E#6|To%8+M|avSUDW@e zQD+5DB@J_F@1?C4>j248GZ%XdfGY=Zu@bwKNPARJdy?lK+1HW6EKi5eW}GAyv_MfP z4|tkGtA4M^jYday8y{}5MW^89Tod%jKt)vj4DXJ%c#SLLsLbe`FE#mldJ@UR)Xz;^ z#mm`c^2xV0mV}4wI}XbH{Np+c^37d5P52OczVfi6cd^dHbFji1H0Rf6zk|`(9)UjwHq}}ZgaWp{4fIm_avlv17oiyJ5L@9 z4A9Dc0ssyNa6@Mz?#t3zwZ{mu(CRAr{o$fu@Hb|5zg92tGk)n1tLeU%dS+PMcyj9m z8xl590|YjwZX%>M?e%hf;O;Aig!q{*O?(wtHJ#|e`4dX|B#V2!%E#-{dA+I={OhBQ zoF8hRYcdo{Fh(;NNYQzNx<40rUK0pfJ?Y;PJa;2i9QR-o zKYwfPtF9~W28bLLF@413JU4r{d7M`$@u~mnWYs-gX}z1FRwe0!ph zY9e>Sf5{@Qqeg?}UW6D2B41l4)c)2tr1^Zel?Re@#kaPS4DR&4Z^?vJ7Opw`;vlWJ z=7rm3_s&MoI#1$WZk}?7z~+6nw-=tbJDtAs1leVqa8SHF#c6_s7FaB5d5fA3`){cY z0f%ob%1RtwQ}_btN%47XtG<0S_E9s7$mJ_9_Qz!-+mX_H7BDxLKcYWY=Hg+X5>t83 z4C?ER+eh50?u_|c^)H$S4C|{l_2r}kEGGT7jr>)vw0{)tRXvXR)d6bkjqHa2CUNnJ zPrPCIvR>oyk=bUJay@=WN92-d^U%(-|9%2sBZXOGdwEE+nXsF6m%v$Ooug;X82~~s zIrho^_?haS@JW1kR|cDp1^%stet}26rk7XJB}G&Gw2t}i#kyX^pYzA-d;%{UnJ*(w zbeqU^eUC1I5*=e|K9yNn{ssAo6_A^+`&)>Qkg)l)3(zZd^}C6&#XRs}y8n=E1FHBw zJ3FG=I=x*qGJ2X0=v~fWy>RpnZti`K_kB}Hv^JPu2*`_{_h@uu<50P-6E;aS~gb# zlI#5?cMIxaCRQDx+qcEP@cRPdp>;VNkTxWc_xd;~6!pjM_#eM<>)xkkQ}fupZ)TD* zs?)#ygz?TSnF}59Po;wO=pZ#25+bZ0tm2Z2%{H%uqemk<9O(X5REf7s8Y3ek z@+Nnj)^eZ0*CY3^PW8YKwOZq62l%$-xrsReAZwaiPL6ZHUI9CYsWlWj{f(J%nC~4e z5zwJxV9;#-ZVlK^%w!Giqcii-9DO5s31?oSw~6#6-~rUpk=)k-;rT57Rl8fzf8U#` zuzjrwkT)d^9hsiS?8Cu0qm(clu|`@y{qz<`dj345X8xkSVoj3kzk30Cva$xGkY(@P zg?!M7ZEi%~_T^szhMT|-9ph#f;^8R+l>fottpU^gm$NvPBqt8+%()%lm_FJ<_Sfca zZw3oQTM`Lcmfi5gmXF(!WRwdP*P=h}H32l0NNNCL*Kt?gN|Idb0=lY2bn*Ea+BeCt z_gBb8YK|+;!oR%)t4dAnP~{ScodH}7ka_N26G)>0t3fgR$1xoZi@wzN44($(|A7g@ zY}ywWJ7cOC?lVS95xsBy>YYEM7Vr; zu*x|l&X3P|b3O$mNf=J3JE(s83=da|LZ0}a6HHd^HxF?3O!HPub+tl>Je1~GpXNd# z7|Et^Q+E9p*~{NFSKR>#@e%j;B9ma6SUu+27wefxP0M)SE@+l7i*0U=f^nLZx`E14 z9*O1FUeUzW!t`ErBtyjJh|oTUG*?R9e|-_Ee5C(6Ch8fb{+(;4{r~NOn!0=?HR7bT z?DkvVl0aSK3LW=nWcTN8bM_Q^P~HP1^!bf{iyA5MpTEs#W6A;XS_ehND(q(W7S6q^ zQvJ0#`E)WdZpr1E|A&a{+z?R~4rJ788c-BJO2Q*o!fAnv+L46|GN_v-k%PH2G7}00177c(BqnWb?WH~c ztF92Mq#a$#E9e*XWuC!+6*R(xJANEF%b%C8q%K3vfe2)@%|E^&!229!KFiH>HZ^I> zjitZ0?(nY=nFYSn@Q`mIUZYsQP4x{7h&u~b6nmR7+1^gZp)y(iX(eNs^;<`2m1gO$ zoea%;rUF)jpWkUyp+r^eS=vx;2%a#XLpRY=#w*eznU|mQ3@=eyzx#QTdz&bk^K*p> zWkTV5Y1btdLSkSx#bB4RluRKgSx=0-mR{53k1B5clmx;)-+DP})lfC-(@QI-@5yu)>?7wPopqtd-*8G>0bDqM@19pwx7M| z!Fsn0U;69ldTG@jgDv!3mM{4)( z9wymNg(bcv417hU*}(u%%??r%UeUdq-Ih6KeJ0@ltot|0U0I9z4GuP^3jW#;u@q(5 zA6cC0-ScVUBTCqU^R3nHsm%SZzsKKsKQELpG|F@>qc~veO|*3vr26d{uC(#=xl>~v z=eGj-N7D1Rb5`-}WyPD`ZE3QF-)y$C;qJ50BI??w2l5Xtx;;o^lvcX+1~FR1_LXrj zqpC)9OUc^1%T1Pp@TGo>Obtx|!PYNi2r3^uPZQoEOwZkl_`5@S9&nn4_7SZ+U34sn zufbQTue`%=RSs--n2c~)bl6j2521%wYK5}==+xF*BvY|__Y`&(;sN6j)01c}37-G0 zE|()Ldtw1ep@kU%@hJW#1>ah$kZk&c58tP^jo+r^)-n}?sqmHhDF0-*aKFrCXnU>C z{By0Z>6@#L=rdE~Rz<9O1zH`JG_sN;2WRw|6;BPLXdUIgNYxulpBJ5e#(=ChZ;(1l zpJ=YUxiz%73kM$~aGVelJw^wyyO%K&QFTlF_+vqYI_7~g{;&DK_V?STKNS1zH=wv! zK(&vB4}f3>{6B;#bVI+Ws69W$uQdO1t_9nQwN90gV?v7H)+Yf;l?CGZ{reAZF+iUS z2Zm`3(^KqYD4}q+_YQlM$rXuGYh&$lOOko21W{=AhMI3qNAI|m%lQyKjfl^unz@?k zrrW=Q=2M1-ve0f`C=<@FdIFO2**w6KQqL=S%D=)%=bb&c-+(qP&li%Xjdm|9rh>51 zmS)Rrq@#5oWrf4NOO@P8Osz{CKOxcrAevXNKHAtZVnJiUr3|+-;Wxe1+A?%ukSqO# zrQs9|8va)sLSpT2<`_r!_#w|&&wc;;nGv!|T1_@v=smj$@D=gPT%TpyEoVzQxTRsm zf3qJq2W5ZeqTi;Yo3n~Hvp2hp9dyk@@g{gr)A<*bjb(eo@&AyPp zf1%H*pN7Se+_LASbT^0~aONuE&|CM8vyIP2cW2y{#tx&JL(M<89@4Gb{ydwh5G=eB zQ>_}by=-KZsq?sgqNqPgT#j1cUK?UPSO3h}Y=PGM+!nuJ6C9_FLC3o5hig@?0fjl| zC3o*P3a&W$Ygzx!4^&>{jFLCAXin|iba{7Ve86pvCe&+-y{xcpH_WR3`Nv@8igYp! zG>i6boZ8i5L1=j-8#mg?L{O_TXA zIjvuCl1K@@5qSpJt>V+-$fPo1hkzSjd4TIZgm@`W?WD)cpN3^ev*miX>z$A9=9IGz zM9LX{8)L?_$D*{|+->Y}foA>@1JEV<1k_U5*zv*Qr;p1w^MYR{=39@EhYcU~$lY8y9=C*e~E=QLoXDK+ni=EZKw0`L<&v@v(hMDttt>@pEmdgE}o(Z3< zX8z>Eefjh`H@TdZc}=hi_JAL+N>aWSn|`q>G2N~*3T`XB5|4RYkqu6-`XkCr95uvb zq#L{scg-R)z<4_xskGfKP0fBn`J@}f&dMTOBpg+O@EPro&N|6wm&1w zAxzI`5QJCtyC$5I3kyczGWUFDr;Z>A0uN=$-j9%dJ36cjJ4}*vRytj^>>VPA0@~tB zundr1kbZM_0Q&H4=(y;E+v83M=|BZNu9DPy3M-y)Q0h^Dqx@Di0o|e#p^}T7e%%_*_cfS6# z=bjK7V|bq1ts1BMQI_+yoG}Ts%9P2 z{X@yOw)uJbvq3a86EGnYX!*QFVVt{`<_Sc$>dQ&yo*L>;N- zQagioY(?0~4?y==#m|*WQ3yQsU^E0DfBU#HVC3i%dQcm!9uG>P#f zGH?tTKtDBbDWHm)y7yJ{%+DAeCwoHNV^WCsWGh{F@XaS$I2!%qH~w#R2e~SJF_6-m zpuFlUZLIxf87v%aFY%_@=SJ%%Q)L6Y`)|Yi!&a zv}Jef*(fDG5(7fXyS&U#m}Dks;Umm-nm0IEK+y}czcvEH2-jLt+g%PkMRJ@%MCUm( z=~9rVzYqWBj1I>(D#FFZh&+C`?b^@uYg5KaWb=_KsT~;>=d}_RuZWV0ZI@7Yh0fYvX*t{VCI-^q zf1sBNu>CEkgXgOjc2ve+AO4U=rjOr84wW-o1*Ha=ABVADqDr{9?8@__Ve98UE#k;n zv2m^DQeIs~`eQ>W;h0c%Qr_A_^O_7EIQo9(1421w>peA{EAX{yDLw4zx+9SIQfuEs`UYm&yui4Uca057H5d?brIEhaQ8 zN5W&y5awrAGJC~_0=fn|4Ze3ay)QwNyVK^dcC{=f0SD;XvPS!x!yn&cw?R4{Ir`xZ zCxs4*%fY8dI|C9P-{GixgmELm&5SP)laxgbl=5IHLWyXy_wNmXRxAAkdvLxvx9a@?#_l7-pRc zZp$*85Y%q@8J78eZA*0BVwly*Ob>+i;jbpPKULBwou12rhWHvTEM=4uS!qX8SY&Zf zhxZYd<%Tro-9^kwHfnE?N>g5SYsY6u8Vel3(;^5=R(wid=E{`QpbRJ%`Q%n8z!OSL zdjAVb2L_L$m5rjW4FQ#{Tu~Nl9(YgU0cVo*|5QMlRpV2zkXl~M#R!J7I{W{$N`eN$ zh*lIYmBt%TwI*2HbMUcHEwG=@Sh;6Ja@DRy^i3P)EvzASh{cFT_v5FVZw0FAWwowu zM!UFix8%jC?oVEkI=vq2#c=iXlLuv)T+iKo&rE>tNIz@UPWfuY92Cl%MoKK)fJM|@ zUc~R;ZVRCbDgz}gjM4tjNf%r|?z8KLDk#ErT#|A(-^9Jck#=JsP`YB}7S9o&!fJ+A zU5G-5ei0$3iFz5)5`!!kp?%ib*4_Cy%vHN%zp3|#!!!!6MtpB2Jn#F9_u&nHiNo_< z`5BskvkW2J##9nU86Y|CkfXkV{ocOg%~DWgM`(#3wCz8U-4+U^wvhsW#y!JEIM$LH zLiQ8n*>gJTGMHwmw<*8>n~BK&gMEQ5wOy;>_G@|J76)}eWCT%_By19nw4Cf8*pk*f zPXh5SMAq&c?)qPN8~0Zk@$Cgl{okx8v)G}n2zd%$WuYVG|MjAcBt%3(=3XQa;RAI> z3o4HP>%jLZy(t(Gd%;p9f4$9e{rdH@C^0dy7S6Ia%4yX97EGq@&}AuSOJy=K_`!mO znVG*{Lbb_83;M62vLtP7Z6Tz%ivJ~5#O5cSk50-Qkl=1j`}ybIbpX3WmD1RT5SBppARK3)?a!)jYKn)O^`wv zk7|E>GvQr}7YpJ`i{Ob{)Fzp+QA14fj8Z_@lo!znDJhZr6Ve{e9TxEiFX@O-lKgBN z>ZDMxUM_oUf(2qcQpc!|JavtDZY)H^;5C+zljdxCR2!{~X^Y%ELo(SPLY z{0;76sQ+~MY6o!oH3jp2c68OEr{$YP2><;6GC0?d{KND5|6a`s=L}ze$%2E><+u3m z#3DC#*Ha*_ZzsFe{HGJknxxR}!&d5jTUtiFP6YnIdNoy_G{#SY%kN?|3^Lzpn8^vs z{)aJ%^RpqiZ)!mX7k?Gb#$k7pGJY1y+a@?D5~<{+&JI%#($|BHd@f5nxjtsTomG{c0Q?FR>G@QM*1iaS4^M30g}4rT1)s_foP;kc#Y}ojeVaXnqkyJ ztM(42_jBFElN?cY93Wr+p;T9D{PIJwhu?yY%7kD;^ic4DB@oB6CiC&?-KBx8+gO)eO%oATByW^( zmg+X;x(akhg~YUO1(5Q$h;L@NSP-~fA3W;#Ml&*aJi4Dgf{t8wcA=No3x&pX%`*;` zMdT!eXDBdVL(yKjuXn_*fJR+-NriHxN%F~B|sBUqGg2)E|>MQ(3LX2+1JhRtzTbUmu#wk4Ew)O{^ zwtxXptwTc)rw!&^0ulCx40dsY4)1Cx6v`;qb~GwhMAaE@Ju!O}dwcqEV%H~2c>9eN87BftOz^N@^=-a<8ia zUkQV7@V+yup$?20qIix6OY#s%C+W?pq9jXqEJrPz$RtedhEKiDb{UD`74G@yn+&Tw zHiL0b)EJ+;44|u1r{#j3_WZHrWL-UdW>sC?!E)3pitZdUY9i+tO|FhDLma%Rho-{} z`FPbPS$Gv>9(`fxEAvzcZg_B6#SM2|jn0lt!q*1F5vYd*Shrz=WyW>-xymkM$J7;b z!WqK1zfKPVlK>?CX^;3Mgkx_&xaN2fZI=}$x#MNOoiIz-qu0Uq5yjf`rkNU)4xY_Em1rp zExBtJmJ+?KmRoX*LD>hw+0s+b*uyq99%6_cv9ms{rQ zB?2yLz^`USNi^}RJrT!FND1?tyBIFs)*pMs7}#Ie)h^er~a3!SQ*b!Mmz^ z$2|`Nhr(n6IJ@Mxe6r&u3GjJ$P5ATTe-WN z5#&Zq$=7^Smw|9ep6ROvaYGtxB#)I<7SJUD9V1b$0q2yW-ygdiia(`Cjo#c|<+K!4 zg}V;;S}o6%uh_l0oBW(X>J*nLi0@9sV$Y14C0>FsHdR_R`yq{ppEG0-)z;XBwtaKm zl~+!(BiKWi147Y=W8p0d%)P6cbH6eyoilv)-!oYy5;kAK^^0Dl(7BO)Z=3NhSgg&B zOYFR3wOGm`w|r!7At)m+fILU?jFrp*)O;~%d-~hH^JI9xju9*Ilc+Nnu;~Gt5krRL zki_wElVg4}qLC81hLun6&(q^PYhEu>Aq^(&JY7t^K)(XR%KZnSuBNCz4DM(2T^bnz zZ*8m8Zho+k-eeF^{>91Eef^8~bR+FK){-3*AVUyk z9GYxbo52i6bsokvJb3};96j_3-LNA@gn(EJ&VkdnM~>6G4LP-J!uBo9D|3bJ4Ze}- zHP$9P`7B<>KdShA{{uf6`KvXB+i*_LxRbAq_rh@=DvxNY1txEP`{v0heQfk*;NIAm z*dG^gW9UbCj@%T{j~xeo{l7yGzx{VFfSd_qccKS^J%spEq7i8|{{8N}&ED=n$*1k_ z<))?`MYgLhl9@%)tq{6nx;msmr?F{T=-V~`HrET;NMW(($P|7Anx(ireTj%9v=iYh z`lJRZc4@!KE0;4!O@=Pn_>5mEn>wD%g`vngZKtM3iVd_HM)?~ z)6d1m3HuBsyrFH5vY>}pzLdzL$z>kF*KqIc^|Z7}FBdVEoedm!6zGv10VHdm*i?Y` zJ>}kELIbSAo}kMWYp5O7eQJIVA*3a)bl1l+VE<_N{#TPSiq4d)KK|Xm(OVcy2&u|1 z0eX4}Y>yh^_``9}2eI-RtvkbTZ@$wX)~|7573wQ2k`%MSA-S;W^aUG#x@@!PHmLgN zxA@aRGU!?bx=aS+Qg!-e?VXx|RYEtNMZw7D#_SCznWT`DwV4uer(J#Tkvjjl?Y27; z&U_V@IMZAP23c$MgRx?#+XE_RY-krFLcGlP^MySKq#%j&lgtI~@VW{5zBys5t{F@c zg7(-A#K~dpkG^Y6|Hz|$J`CCwn*x-XE+|4!p{3C@HdBvtn_<9ZP5jB!)r^UH>w9Hg z;O}z0XF9y?;Q9`_{S*5|e+2?mKOe;KFgYp*k?@BP}0iNBZ?7~JmFyen_)X~2qFK+U`3TkVGhP=yb z+~FbtIve9@xrXTShCX!HtzB&i*NMqMws8i_YU;)Gp0Tc9#b(gF5hVbBc+Tl>&--`BL4YLm9$sPSvT5^9~UIE?X``E$0NW$Kf8XMv9qn0kozIDhihQh8$qdAoB}F-N$MK}lelL44*6z+&SduYWX*LlZ^w`X_p%ld-)pP+ z*H*a2C~7i#DtIOTGSRIQl(>{HyW;s70&SM{QHo)ukHvaENN;MHXf+jEAcZqdojU(dQfUI|D&5XZJoR^k@-*%Bh<(?9Be zdGesle8czuDeXCZCQUVBAkft;N1B&z}(g`FH5kaJhO0R+! zQF@mWkd9#JAT3B0=?N{6@+RE-t@YM=-&*fm@BKNPb259+?lZH`o?)m1udf6dTW(zt zKOc z6nwH8^HZi*xSj^zLqIN2)v1dERGTn7Y0;CW1uz=Lf3@j!?0bpEV+=e0G^~gh#8c<* z**Znc;0`s{XHBXce9z=#F1v5^7%I7gI@8KLYWO^@azC7ZfWdAs?+h=R>;^qzX_Uwj`IWaFG< zWeV0=1x}{tXLC8EIieNfwZ~acl8GCl~*En3;88@^J=54lm+hnd{LtZ>CUGZZ_-zPAJF>x8db3R(K8bzKu^<^t#MnU&BE_BA_ClRjY zxqcm#%s4xA8waimK4y}EpR93D8|8npCVPtP2Np;+ypZtF^*2r7590))%*Ia^s>*}F zov+R5mOAlE+lbgE+H%bnqYFP?ANve%Qu=+77x>738`vR{gW{#4Wm zcC`))yzT^XIO`7PW8>T8?NhAY;jqJ$S>2|%OAHW`%h>SamC{*IB=$=@ivLIi zzpBWzOxCJ&LLVgcEm)bJH0?fZUnI0wmd2lO(9nxfke<@~X7aC0YDkL})bswwVKue4 zgVgWs?NKsl&lpxsdX`n)z2yUoO0xz2P|wyhtkvB|`(~?x+BYIiOLOlaB)gCs9GIfZ zmsYUGv8+c^X}?B==1&6Tf&3*60B|QoQSe~6+qS=fIu}47U|-9sps|;|rNiCu^A4-sHULk0+rYceWf%o`ezAYuxQt&wraj- zM)eg0_9ZtRIvUg7$Gn(^tfrnyC5zkwWXfK?cPZ`op1fAo6jNB@+{ykmgY>pc>EgVy z5N`yMtu7XF>V^0g;U`5nT^XTKg>|^%Pa!(ztG5*$pns-ftdPgb_?d?fA3~V8xZ@rj z3#?Eam3$oRr0f9zhO!hqo7PX0;MGpA@d2vy`*;csLACe-}G9?aTA(xq)X78 z+dt)$U=#6;&G|nA8!1vOdudw!ecTV#mS6oQt_~KinFte2^~UY=$Ff z+3ucwtJ$!L+YxXv?!BT;O;z?aTRUqepFXJdDvH$@O!je zf!Bfi^-EJ`WRU`0T)#@~PBL9_T>c)I|8sFOAS|}#{QYbFAj^f_y%G6w&%%N}+g;tY zm6F6Hs6GQFv%WV_B?_^0fos8N<&LRF5B9{wz${`eYuq{O9B$AJC=NV9eg{+TjXWzWIo% z(225KnT7BDj`9mN&0}}J@rp5byoQV&V1J|WOG8gHLgr$f-_?hs1*9mYYdwXZS_JpO zoug0|<^D0fa>?~O%{zV-6L)L2plcVI_hhRbUcDLtlCZ(&x@*l5;Z`DHPnL+%+4ryT z4JyN*(;q9VhjYE&?zHX9Wz%l|c9G^EW(SG#z%It8`b&Hy%=co)ETLz8vStwUp@mc9 zW|U-m`{zREUS4Pu!D!rwEu^gb2&5%V&B36hWydf})zeRY7538Ak1=73V}sGg+ z4KODdO>=31B@%BBA{ioi7#JFrVBdiE>_Ex8UM;x|e>I7?-V@-bdlyzF^K9y|$?+iy z%)@x!Z(vpi`$FhM2`yh_^kJyn#h*#Y z{%HtK)tI9UkF+4N3uZBn8TMvR-a%eIf}qrZ0kfk72~q(R-T6K<*1e|3ji-NLmVLReg>}JYo{3?=`n_nt>SPQ*V?M~CE zOc4Khl2B$AivK~m!$y_C{+CW8S6Nu5)UUC!wn? zxT)yzWC(iB*VfFZ$)MZe9Hxsv@2`{6^?I=iEo!9%#BVJh5m#zy#DYGiXJ>EYnwpz4 z($Z8Q{{2t@X@Spp+_6%>E_DHY%$fC2*P9m!o8GhD)fp6e@KQjbo-Dl=z4@9AD)fkI0;kje1jLP)-vi!`)s+oZ&ij zC?Y54= z#j}0@0?f|K(=07D{Typ2gv;~s;YN)Yd{UPoZKHR#}`=B$`+i{%T*q~+S=8=CrRZwxWrW;A$47x1k% z$%n*QkJCzi21gtV0G&hCSI=yXxu=#Oa+(%om{8=gD(@Tm`h|i_cbNSB8}VVUUJ0oJ zIvbM}XhHp^q2Io#_4W0o0d_Prn)b1=srmX|h0?OKj~5meb~S7(ts$yz-}4tfxr@M8 z5_Js>`zL-Rqi@gUYsS6zeS1Iy>Or>u1_lObJMp7{ zox?7yr`<83B=?=0DvQdwcqPX?Jp+T+or%4Lmi7LC|s2b=K6%*s|1LdvkzChs)T zp41Cf7mic5uYyehUe|XA`7d9-%mk(t)zylN<29$ixk7!pRV^o+IG&DHr~{um`!V)P z9_t4_r*$7Wmr>CI$)8}!DRD0COgMU>EH~VtTsu(?2r@9GIe!LFq)m$N`cvb@t%MTx zp__fd_6uhA*+ldEmoJD_UJMEPz2n|0 zt>*0S?;j%HzcvJir!I5=H{${mQEjJOF(Kki0{GDlqWJ& zpc8%PT+LIdMskC?aZ<2U)XsU+l5LLVQIZJ#@9WDbZ@i=NrFPqIV_U<9ugUQfpKyAQ zGjM@ZlT*CcjW!WuC*_tW^U||<8uFA6S*!Z^NioIU2wX{2V1dM8+0aVHSKnP*|LFM8 z(NXgxk2!Jqh{=C);;NVio0rT`P(?xoEdiX5{z;o)PyEPeeyw9U%Rv6*;OXCipT-=n*k|_Vz2lu%j$K@ru zXTuQrQ?4CIjyjh%75n|^JMG+KDB+Vt1UNd5*otOtTst){{VRm7m}3{?Tpc`JwHnu7 z)dz^LG)Q8pXLG}CtyeVew^<7SAHG!c=m~KXcZEvXZ(#O$&xUjQR}$7!c;^n>n8G65 z@9o?vz_rA6TS+cGAx7avZ@y@@bz!Tr}I z6J=?B)FmT}%D~;>o!ioX<<}0s0N&uSGIXeRY4}!g5|GzCqUHxvwLLlfj2NV@shPmW z#ufx#UkaqOb8in&;RXQuC7*Aj$NF!5)kyRa-Phw8PZD|l*t zYSq7bD0!VZOw{p7qO63UzW%53nryub83m2(>0gufZDhD)oEpA(RlKAM93>L$tmv{F zRLrxcr1MeQ*5(HpjmC4{?3sFOlOhvt0ywf*2;aGdx zIjIiL%GRCow%i`LpS(8dgAF*t15Vh*_fK0G=d~8&lo$M0>sfR0itBpFK$(~fQmw%Z zGD8V#oRqOnCgKcphSc?E$T3zQa<5mhYN$W{R5^@NTohKo4$dOkRpqOX)ZU!4C66<7 z4OW#Mke3}Om!xXM;F1-)A8Swr6ZCc$8YYFT4`kr0i`v|iXZfz2!=qsMoC6#Y2 zA2VKF>~7?*TQavbW&D=cf7LiEZkwPtKpt;X$c94Hv7;%oi5rbdHhQqt5!-ZTsH<_M zxo*$`{b2@`Tih9g*(8hdZAqi#x29b^cJEeIGu5cozlG%8H82biq<4NClRm2o{hr#lal>vQs!Z!>6}Zv2gXniW!==#U_KT?4BlS{ zMR*AshAR?mji5WLqUq1$Ts67TT&=pOLxh-A^4MAQCQ14{L>Iqcea%OkQ}1>J0Qxpv zYDrWo`TmS9MYyh{A?!A- z`v8ld#u%flciqGm{J2O<9V;$l$q`xr6kki$HUdaT@a`$g0V9>Osq#6 zqb(9 z^6PIpMp@{h+^=hw16{$V^EFY}e949F*y)w?>uebOuv(wjjQ6c+insFNL(y6NcgkSsg=evsH7*-z7h$?)4%n?Uisny9fUi@OhM z4oI~nTt7xo<|8cpu#lS;!P=P8nY0h_5QXd)tL;8!c@awi9G+!b1WwwBK)&0vU z((~9=PDn^{`^E>8zY=3%bUD{dS)8o+$3yrtGVmr@b^vlw4LDKS&WC9Om!D-eL(k5hipspN!+1;aA3o7O8=Sjfqo-<}!SEs?vi>ZmbNuQJLAbS2uF zk0dV$uCl3g|LW;+K1lD3oe*Ooy)Fydb=1v>3)VZS#+KJ{j~uEyx8bLIIF`7Plw>omvjojtjoz~NZv zf*{xmg^@pvGZqB6=bM|ke60Ll_61vM|F>ISq`T#Gq#@L-o8<9rK&?Z*o*Wy5gg^X+ zv#6Dr64AL9dhmE4d2=omc+8n$CDDKya9$3a?xS9FD~-+M&{&9^*>BK3z8x5aH6?w> z`=l`zL7vuSB%PqW`LnPT4Z^VXQ>T&{W3ER$n6)D!XNb@Bf;W!$widadSx8Imt|@Ci z;YwI7*{c=&A zwR!*Pr)DPKF`zTNII!*AoJ%(ra$`j9AmGub!E7y zO{JMzFheSQ(rd0GUg$4N&gU6yCRa#5%S=W_?lN^W;%UV2!m{$LeQ}{Qsx$U=X#lSV ze{zBY5Gu*1MD7yW&D6z=u;nUD zO(ul276hS2rK{Z^*|?}q>ui$Tkzx}(Isxzhc)5(%<9B^d(6!fJw*2;2z0yx-i>It* z*9hCx^o}ygn&(LC-`%EOBeA18zUrF?Ryv$^1)qxD&-o1^Ez92j2!K|2QP=vHs;i%4 zw{RNgfS_{5Ork2A@6dtfR#L~dJfKayRVY5*tQ8zv+m(1DtjyDLqhu02jCKbVYA7U=GY%c z6x5Zm-3|eirs~#6N6OZsiP!R>XxvPjXC%DP>Zk9c8{M|aDf8j*D}*6Lbn%=ik>@Y# zo9cj!?@Qoik$&cE0J*S+j&626qgOKVv8qHVP>g!82DnboCO4I?l?X5Ju3(GX=3})h zlgu)aP)G53=@T9a`#Si~$Q(NDI}^V-_oM5feP^lzjZ`DA>pl8cs-gt(4G?H%!_jB+ z&Dw6dtE!PB%|~JO{^ytXEB|&Ii90JL4i7;Fa{7(RecU&iggCmz#JBFldrbo7!)m;u z-gT=ht;WYu!$!jV-E2HFX}NT7^rt^InTZu{PI)8j>2iiFi@T7}n=iRL9OElkVzmF$ zoiFCYp84dJPs)`g3kcHm`h8zoa^%^7fYav~DWRzXbh4lV!&ym256(#t?3c6}=9DQ8 zV^gS+k$>FX^8xhL_l8^|i}zk^FdXdv4t+10y#~Df!le10Ll5y6Kz4lQAOOOlA&GK% z1_l}5#TDCol_aTL3WSN3#j-=9r?#dko%dMJ;U()cIr%vs@Ib`FV!2qia1a4nnlcZj zIhlYl_@5c?eM6{(cFi8g5Cc_YF0f%Xx1Nj8`J8CkVMwENI_>q&6bc&W7yix4 ztw}LR;{k%>n#*|1QQ+dNm>fdDxwP!(JFP0{bVe7WSns_!T}$?oAe8A+y{SfvKQxy(3VMTgj+*LYbGe7W=-ml1V+wN|8p=r_XmV*uAD?8b( zJN5X2wJf7$+iI8T!Dv3HWSIZ#W!VwY9Yr>cJtQBhmSGr)-OVMj9k3c=o*0BN{->*< ze>)2Pzt>p*7Hhg1yULPXuAzddBe(BC2(mLUfj!YBzXhgDXgUn8|Ldv_0K_RPFKq_^ z=%VuU*5}e|jF8@e_W<_)xETDOgo^6@i$+kZ2L*^i%x-%$s*L|fYtXLX3|*Da^HaS# e-E$DswLoL<>KlCVeUJcXgl=i*sOPC!2L2Z%BvESs literal 56060 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 DqkAci diff --git a/icons/obj/supplypods_32x32.dmi b/icons/obj/supplypods_32x32.dmi new file mode 100644 index 0000000000000000000000000000000000000000..a7607f716f7ad0e981cedd29b08ec0a0e4b5090b GIT binary patch literal 1640 zcmV-u2ABDXP)V=-0C=2@%{>dkFcgL1Irmpw+Fkrwvq*{rY!^3|kdmtnO%2?{ivQjr;3n;M zUU=ZtcxukNL)B`SiIa~{lz7~1LA<@o14=yXK@8*{LoSiB&4dywe)WAI6KCRJKNSe) zIibWIh&$Vf%Kq`oZ3Qvc;2rUzEZ>VPO>rcXvgN$a{!=wJokG=W_yQm$R#W|7@{9lg z1&K*SK~#90?OQ=>+ejFFO18Tub=G$3H9ffsghHUWNpAWj!H}>UvJldoyj~xI(aD$| zO6e&HIXjCml)aX{`KC)qvnH@m6e?V3i`TU!)-Bl$MtcY|*pA{@wycrsc^BClX+G(_ znQt^R-zStxC2;hQhxuKrSNWUlQ^84n-wgLSY(dCy`qG2i>Bp9nYk@HrzzaOg?^;d- zZojsld&fqAep|a+y~2%p-ejK&x78!~eGC|sh+znH+Ukd?)7JvrMnpd(J0)`zody8tij_Oj*69?&ly=65YOTDF(u1D!q6 zR{{+3&F%Dhzquk`2{4K9-5B6Cymmj`ws{X=3@!j;_+l-ndj-+%znz=tH4`TH+*Y@n z?=c8?4`2*F2=hUBvf;S^=~Tv76Cu{{T!8JJE#z`}TM@jNrFPq3H9ID*j|z}ZWh_n8 zM*1L{rdftzz^EIeG&5G10)RrHU~TVgK~ai0K0Y3a2%4r@ic&=2+W?kU61J|$X=`-4 z7{_wJy$Mt4RL0Wv3KXRXScQaUiyo|t102lvVbc_vlEtZrK0du*$_2J!nOVc##tbAr^nr5Az zlws5j$Z}SA{^FUCPGziKt>5hy=||T?Ae2faq*ED-v`XTuMKKNlkR%DJszTQ*NG4Zt zuy=@Ge@WrGjaS1cs%gQyD8f7lu(c5S|MIxN=ixVPRpxGhgdPZ0In(!S&EYw_5W!!RZpV)8+1oUk!B% zx5LwYFF-ID+y{VYG>TfShG;ZO%YL|b4*>A@r%&`8xu#aD0RWoK<`X~L=gZNI!3AIp zE&yY20T_b|z!+Qr#^3@l1{Z)axB!g71z-#=0Ap|g7=sJI7+e6x-~uoP7l1JwZArGr z^0aTfZ|`j3VDFG#H;G%9gQBMZgm6Oe(n^As5%QN-5>Qo@mPwLCgSCh#T#%b#>w?m$ zj70*d!gFD$s!Bss2=U>$FeUx;q)g8r>>c7@@32FZLZLt-;dp*PC$0(5PSAt-2N1ki4Y`ki<~3p+l2wp@jIQ$SiSNC>@dup?y&p8UVyqFLJlFU(J&E-gy>P%D-gvv zB9RbG)1;fvrfDJ+3ejtcFx6@mu~>{6T3Sg!k|gl_fKFV?4_I7WgsxX8>4fl5=<2_& zS2}k7Tdhmf>ouI8pTjgwG#Vy!y@FUQhHAA6;3{g4#E$mL512&Q<_9E`t03Xz1oe6i zkw^%w)+GSYu#0xIT9=rbx;hf_tJNxux-nFKz#y(<9NZ7kM}EM|-(L=8V0LyEu~-aF z@&j%{Fc{qLCoacL@<$yPM8^OUKK|j|dyC`;7>0p=%+Db1$*3Ec4c+aiU~O#;M@L7P zp1y-t>k`w`cW8(@iIVRA93?H^sSwR(^GUPWgeZ#pcICRRk439*hp+Pk{D4vS0N>{a zu*S$;(E9rNHDY8rEA+|_`0MT4j{E>oj6>He9kb(cC*CXExD;VWTzh^%`;6EwKVW@* m6p#D$f#(M>249Y5jQ;_!pk6}%TTlZ40000 Date: Tue, 7 Jul 2020 00:22:05 -0400 Subject: [PATCH 030/196] reverse sprite fix and no more message_admin()s --- code/modules/cargo/supplypod.dm | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/code/modules/cargo/supplypod.dm b/code/modules/cargo/supplypod.dm index 6e1b76157da..dc566c3114e 100644 --- a/code/modules/cargo/supplypod.dm +++ b/code/modules/cargo/supplypod.dm @@ -101,7 +101,8 @@ /obj/structure/closet/supplypod/proc/SetReverseIcon() fin_mask = "bottomfin" - icon_state = initial(icon_state) + "_reverse" + if (POD_STYLES[style][POD_SHAPE] == POD_SHAPE_NORML) + icon_state = initial(icon_state) + "_reverse" pixel_x = initial(pixel_x) transform = matrix() update_icon() @@ -186,6 +187,9 @@ density = TRUE //Density is originally false so the pod doesn't block anything while it's still falling through the air if (landingSound) playsound(get_turf(src), landingSound, soundVolume, FALSE, FALSE) + AddComponent(/datum/component/pellet_cloud, projectile_type=shrapnel_type, magnitude=shrapnel_magnitude) + if(effectShrapnel) + SEND_SIGNAL(src, COMSIG_SUPPLYPOD_LANDED) for (var/mob/living/M in T) if (effectLimb && iscarbon(M)) //If effectLimb is true (which means we pop limbs off when we hit people): var/mob/living/carbon/CM = M @@ -274,7 +278,6 @@ /obj/structure/closet/supplypod/close(atom/movable/holder) //Closes the supplypod and sends it back to centcom. Should only ever be called if the "reversing" variable is true if (!holder) return - message_admins(holder) take_contents(holder) playsound(holder, close_sound, close_sound_volume, TRUE, -3) holder.setClosed() @@ -374,7 +377,6 @@ return glow_effect.layer = LOW_ITEM_LAYER glow_effect.fadeAway(openingDelay) - message_admins("HUU!") /obj/structure/closet/supplypod/Destroy() open_pod(src, broken = TRUE) //Lets dump our contents by opening up @@ -404,7 +406,6 @@ /obj/effect/engineglow/proc/fadeAway(leaveTime) var/duration = min(leaveTime, 25) animate(src, alpha=0, time = duration) - message_admins("HYUU!") QDEL_IN(src, duration + 5) /obj/effect/supplypod_smoke/proc/drawSelf(amount) @@ -555,9 +556,6 @@ pod.layer = initial(pod.layer) pod.endGlow() QDEL_NULL(helper) - pod.AddComponent(/datum/component/pellet_cloud, projectile_type=pod.shrapnel_type, magnitude=pod.shrapnel_magnitude) - if(pod.effectShrapnel) - SEND_SIGNAL(pod, COMSIG_SUPPLYPOD_LANDED) pod.preOpen() //Begin supplypod open procedures. Here effects like explosions, damage, and other dangerous (and potentially admin-caused, if the centcom_podlauncher datum was used) memes will take place drawSmoke() qdel(src) //The target's purpose is complete. It can rest easy now From 1e4ce1e4bee33b5d4e01827cb79d0234f761ac9f Mon Sep 17 00:00:00 2001 From: MrMelbert Date: Tue, 7 Jul 2020 01:52:24 -0500 Subject: [PATCH 031/196] lol --- code/game/objects/structures/railings.dm | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/code/game/objects/structures/railings.dm b/code/game/objects/structures/railings.dm index 19a943d20f4..d7b0958e8c5 100644 --- a/code/game/objects/structures/railings.dm +++ b/code/game/objects/structures/railings.dm @@ -67,20 +67,21 @@ return TRUE /obj/structure/railing/CanPass(atom/movable/mover, turf/target) - ..() + . = ..() if(get_dir(loc, target) & dir) - var/checking = UNSTOPPABLE | FLYING | FLOATING - return !density || mover.movement_type & checking + var/checking = FLYING | FLOATING + return . || mover.movement_type & checking return TRUE /obj/structure/railing/corner/CanPass() ..() return TRUE -/obj/structure/railing/CheckExit(atom/movable/O, turf/target) +/obj/structure/railing/CheckExit(atom/movable/mover, turf/target) ..() if(get_dir(loc, target) & dir) - return FALSE + var/checking = UNSTOPPABLE | FLYING | FLOATING + return !density || mover.movement_type & checking || mover.move_force >= MOVE_FORCE_EXTREMELY_STRONG return TRUE /obj/structure/railing/corner/CheckExit() From 3bcbf716989feeea396c2d7159593fc1abf083ce Mon Sep 17 00:00:00 2001 From: Wayland-Smithy <64715958+Wayland-Smithy@users.noreply.github.com> Date: Tue, 7 Jul 2020 04:12:52 -0400 Subject: [PATCH 032/196] Sentient blob mobs legit mass status --- code/modules/antagonists/blob/blob_mobs.dm | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/code/modules/antagonists/blob/blob_mobs.dm b/code/modules/antagonists/blob/blob_mobs.dm index bb79a4e9eef..5871fe51bc8 100644 --- a/code/modules/antagonists/blob/blob_mobs.dm +++ b/code/modules/antagonists/blob/blob_mobs.dm @@ -34,12 +34,17 @@ verbs -= /mob/living/verb/pulled else pass_flags &= ~PASSBLOB - + /mob/living/simple_animal/hostile/blob/Destroy() if(overmind) overmind.blob_mobs -= src return ..() +/mob/living/simple_animal/hostile/blob/Stat() + ..() + if(overmind && statpanel("Status")) + stat(null, "Blobs to Win: [overmind.blobs_legit.len]/[overmind.blobwincount]") + /mob/living/simple_animal/hostile/blob/blob_act(obj/structure/blob/B) if(stat != DEAD && health < maxHealth) for(var/i in 1 to 2) @@ -128,7 +133,7 @@ if(factory && z != factory.z) death() ..() - + /mob/living/simple_animal/hostile/blob/blobspore/attack_ghost(mob/user) . = ..() if(.) From 96a8e978c989bc828f827f5ec1b0473f65facf57 Mon Sep 17 00:00:00 2001 From: Kathy Date: Mon, 6 Jul 2020 19:03:15 +0200 Subject: [PATCH 033/196] Surgery is only visible one tile from the surgeon --- code/modules/surgery/surgery_step.dm | 1 - 1 file changed, 1 deletion(-) diff --git a/code/modules/surgery/surgery_step.dm b/code/modules/surgery/surgery_step.dm index a6ad043e854..d87f8a4af80 100644 --- a/code/modules/surgery/surgery_step.dm +++ b/code/modules/surgery/surgery_step.dm @@ -180,4 +180,3 @@ if(!target_detailed) detailed_mobs -= target //The patient can't see well what's going on, unless it's something like getting cut user.visible_message(detailed_message, self_message, vision_distance = 1, ignored_mobs = target_detailed ? null : target) - user.visible_message(vague_message, "", ignored_mobs = detailed_mobs) From ef8ac20b725c2bea0482e4ab8bdd5e8d039ba5ac Mon Sep 17 00:00:00 2001 From: Kathy Date: Tue, 7 Jul 2020 12:28:43 +0200 Subject: [PATCH 034/196] Code refactor --- code/modules/surgery/surgery_step.dm | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/code/modules/surgery/surgery_step.dm b/code/modules/surgery/surgery_step.dm index d87f8a4af80..705071776c9 100644 --- a/code/modules/surgery/surgery_step.dm +++ b/code/modules/surgery/surgery_step.dm @@ -174,9 +174,7 @@ chems += chemname return english_list(chems, and_text = require_all_chems ? " and " : " or ") -//Replaces visible_message during operations so only people looking over the surgeon can tell what they're doing, allowing for shenanigans. +//Replaces visible_message during operations so only people looking over the surgeon can see them. /datum/surgery_step/proc/display_results(mob/user, mob/living/carbon/target, self_message, detailed_message, vague_message, target_detailed = FALSE) - var/list/detailed_mobs = get_hearers_in_view(1, user) //Only the surgeon and people looking over his shoulder can see the operation clearly - if(!target_detailed) - detailed_mobs -= target //The patient can't see well what's going on, unless it's something like getting cut - user.visible_message(detailed_message, self_message, vision_distance = 1, ignored_mobs = target_detailed ? null : target) + user.visible_message(detailed_message, self_message, vision_distance = 1, ignored_mobs = target) + to_chat(target, vague_message) From 47ce05dac9eb11df8a7eb0a46ee34a23fde8e2a7 Mon Sep 17 00:00:00 2001 From: Kathy Date: Tue, 7 Jul 2020 13:45:27 +0200 Subject: [PATCH 035/196] Fixed logic --- code/modules/surgery/surgery_step.dm | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/code/modules/surgery/surgery_step.dm b/code/modules/surgery/surgery_step.dm index 705071776c9..13e97feba7a 100644 --- a/code/modules/surgery/surgery_step.dm +++ b/code/modules/surgery/surgery_step.dm @@ -176,5 +176,6 @@ //Replaces visible_message during operations so only people looking over the surgeon can see them. /datum/surgery_step/proc/display_results(mob/user, mob/living/carbon/target, self_message, detailed_message, vague_message, target_detailed = FALSE) - user.visible_message(detailed_message, self_message, vision_distance = 1, ignored_mobs = target) - to_chat(target, vague_message) + user.visible_message(detailed_message, self_message, vision_distance = 1, ignored_mobs = target_detailed ? null : target) + if(!target_detailed) + to_chat(target, vague_message) From 750e50aee35f21afce4a97d24dde2182fb9481ae Mon Sep 17 00:00:00 2001 From: MrDoomBringer Date: Tue, 7 Jul 2020 09:34:33 -0400 Subject: [PATCH 036/196] fixes rubble a bit --- code/modules/cargo/supplypod.dm | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/code/modules/cargo/supplypod.dm b/code/modules/cargo/supplypod.dm index dc566c3114e..220a7c461c4 100644 --- a/code/modules/cargo/supplypod.dm +++ b/code/modules/cargo/supplypod.dm @@ -269,6 +269,7 @@ if (reversing) //If we're reversing, we call the close proc. This sends the pod back up to centcom close(holder) else if (bluespace) //If we're a bluespace pod, then delete ourselves (along with our holder, if a seperate holder exists) + deleteRubble() if (!effectQuiet && style != STYLE_INVISIBLE && style != STYLE_SEETHROUGH) do_sparks(5, TRUE, holder) //Create some sparks right before closing qdel(src) //Delete ourselves and the holder @@ -355,8 +356,7 @@ update_icon() /obj/structure/closet/supplypod/Moved() - if (rubble) - deleteRubble() + deleteRubble() return ..() /obj/structure/closet/supplypod/proc/deleteRubble() @@ -379,9 +379,8 @@ glow_effect.fadeAway(openingDelay) /obj/structure/closet/supplypod/Destroy() + deleteRubble() open_pod(src, broken = TRUE) //Lets dump our contents by opening up - if (rubble) - deleteRubble() return ..() //------------------------------------TEMPORARY_VISUAL-------------------------------------// From c89a19745e8c9ff078c9e4a4057fec179e5c94c9 Mon Sep 17 00:00:00 2001 From: MrDoomBringer Date: Tue, 7 Jul 2020 12:43:11 -0400 Subject: [PATCH 037/196] fix reverse icon --- code/modules/cargo/supplypod.dm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/modules/cargo/supplypod.dm b/code/modules/cargo/supplypod.dm index 220a7c461c4..21ad6273059 100644 --- a/code/modules/cargo/supplypod.dm +++ b/code/modules/cargo/supplypod.dm @@ -102,7 +102,7 @@ /obj/structure/closet/supplypod/proc/SetReverseIcon() fin_mask = "bottomfin" if (POD_STYLES[style][POD_SHAPE] == POD_SHAPE_NORML) - icon_state = initial(icon_state) + "_reverse" + icon_state = POD_STYLES[style][POD_BASE] + "_reverse" pixel_x = initial(pixel_x) transform = matrix() update_icon() From d356db90236de2ae751ee69e262a37e6efb70226 Mon Sep 17 00:00:00 2001 From: silicons <2003111+silicons@users.noreply.github.com> Date: Tue, 7 Jul 2020 10:29:29 -0700 Subject: [PATCH 038/196] maybe --- code/__HELPERS/icons.dm | 4 ++-- code/modules/photography/camera/camera.dm | 9 +++------ .../photography/camera/camera_image_capturing.dm | 8 ++++---- icons/blanks/32x32.dmi | Bin 0 -> 216 bytes icons/blanks/96x96.dmi | Bin 0 -> 222 bytes 5 files changed, 9 insertions(+), 12 deletions(-) create mode 100644 icons/blanks/32x32.dmi create mode 100644 icons/blanks/96x96.dmi diff --git a/code/__HELPERS/icons.dm b/code/__HELPERS/icons.dm index f78f95db29b..12a2c733c42 100644 --- a/code/__HELPERS/icons.dm +++ b/code/__HELPERS/icons.dm @@ -712,9 +712,9 @@ world /// appearance system (overlays/underlays, etc.) is not available. /// /// Only the first argument is required. -/proc/getFlatIcon(image/A, defdir, deficon, defstate, defblend, start = TRUE, no_anim = FALSE) +/proc/getFlatIcon(image/A, defdir, deficon, defstate, defblend, start = TRUE, no_anim = TRUE) //Define... defines. - var/static/icon/flat_template = icon('icons/effects/effects.dmi', "nothing") + var/static/icon/flat_template = icon('icons/blanks/32x32.dmi', "nothing") #define BLANK icon(flat_template) #define SET_SELF(SETVAR) do { \ diff --git a/code/modules/photography/camera/camera.dm b/code/modules/photography/camera/camera.dm index bce6e914faa..26a50fa50a3 100644 --- a/code/modules/photography/camera/camera.dm +++ b/code/modules/photography/camera/camera.dm @@ -202,14 +202,11 @@ var/psize_x = (size_x * 2 + 1) * world.icon_size var/psize_y = (size_y * 2 + 1) * world.icon_size - var/get_icon = camera_get_icon(turfs, target_turf, psize_x, psize_y, clone_area, size_x, size_y, (size_x * 2 + 1), (size_y * 2 + 1)) + var/icon/get_icon = camera_get_icon(turfs, target_turf, psize_x, psize_y, clone_area, size_x, size_y, (size_x * 2 + 1), (size_y * 2 + 1)) qdel(clone_area) - var/icon/temp = icon('icons/effects/96x96.dmi',"") - temp.Blend("#000", ICON_OVERLAY) - temp.Scale(psize_x, psize_y) - temp.Blend(get_icon, ICON_OVERLAY) + get_icon.Blend("#000", ICON_UNDERLAY) - var/datum/picture/P = new("picture", desc.Join(" "), mobs_spotted, dead_spotted, temp, null, psize_x, psize_y, blueprints) + var/datum/picture/P = new("picture", desc.Join(" "), mobs_spotted, dead_spotted, get_icon, null, psize_x, psize_y, blueprints) after_picture(user, P, flag) blending = FALSE diff --git a/code/modules/photography/camera/camera_image_capturing.dm b/code/modules/photography/camera/camera_image_capturing.dm index fa12cc20a7d..9b3034c8765 100644 --- a/code/modules/photography/camera/camera_image_capturing.dm +++ b/code/modules/photography/camera/camera_image_capturing.dm @@ -51,7 +51,7 @@ atoms += A CHECK_TICK - var/icon/res = icon('icons/effects/96x96.dmi', "transparent") + var/icon/res = icon('icons/blanks/96x96.dmi', "transparent") res.Scale(psize_x, psize_y) var/list/sorted = list() @@ -60,7 +60,7 @@ var/atom/c = atoms[i] for(j = sorted.len, j > 0, --j) var/atom/c2 = sorted[j] - if(c2.layer <= c.layer) + if((c2.plane <= c.plane) && (c2.layer <= c.layer)) break sorted.Insert(j+1, c) CHECK_TICK @@ -76,7 +76,7 @@ var/atom/movable/AM = A xo += AM.step_x yo += AM.step_y - var/icon/img = getFlatIcon(A) + var/icon/img = getFlatIcon(A, no_anim = TRUE) res.Blend(img, blendMode2iconMode(A.blend_mode), xo, yo) CHECK_TICK else @@ -86,7 +86,7 @@ var/yo = (clone.y - center.y) * world.icon_size + clone.pixel_y + ycomp xo += clone.step_x yo += clone.step_y - var/icon/img = getFlatIcon(clone) + var/icon/img = getFlatIcon(clone, no_anim = TRUE) if(img) if(clone.turn_angle) //the cheapest (so best, considering cams don't need to be laggier) way of doing this, considering getFlatIcon doesn't give a snot about transforms.' img.Turn(clone.turn_angle) diff --git a/icons/blanks/32x32.dmi b/icons/blanks/32x32.dmi new file mode 100644 index 0000000000000000000000000000000000000000..6c4f2b33e0fee9d3f99ff3febc0760cfd078aed2 GIT binary patch literal 216 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnL3?x0byx0z;m;-!5Tn`*LkmkKF1;}MA3GxeO zaCmkj4amu^3W+FjNi9w;$}A|!%+F(BsF)KRR!~&>{Y!Ac$FEPcymhtCojD)8A=Kca z@q&JF>8>?&JF>8>?r978O6lM^IZ7bl4HG(F^GV0pm6sL^WE Q0hDF%boFyt=akR{025G63;+NC literal 0 HcmV?d00001 From 3f50204b4805eb2cee9fc4ee1a3d0527cbbc25e3 Mon Sep 17 00:00:00 2001 From: Unknown Date: Tue, 7 Jul 2020 23:30:39 +0200 Subject: [PATCH 039/196] E --- code/modules/antagonists/eldritch_cult/eldritch_effects.dm | 1 - 1 file changed, 1 deletion(-) diff --git a/code/modules/antagonists/eldritch_cult/eldritch_effects.dm b/code/modules/antagonists/eldritch_cult/eldritch_effects.dm index a305d3e05d3..8c60a1f335a 100644 --- a/code/modules/antagonists/eldritch_cult/eldritch_effects.dm +++ b/code/modules/antagonists/eldritch_cult/eldritch_effects.dm @@ -43,7 +43,6 @@ if(living_in_range.stat != DEAD || living_in_range == user) // we only accept corpses, no living beings allowed. continue atoms_in_range += atom_in_range - atoms_in_range -= atoms_in_use for(var/X in knowledge) var/datum/eldritch_knowledge/current_eldritch_knowledge = knowledge[X] From 7cbbe3502c7a7624969dd660c5608a4d6cba322c Mon Sep 17 00:00:00 2001 From: ShizCalev Date: Tue, 7 Jul 2020 20:23:01 -0400 Subject: [PATCH 040/196] Fixes runtime when doing things with a ballistic gun without a magazine loaded --- code/modules/projectiles/guns/ballistic.dm | 5 +++-- code/modules/projectiles/guns/ballistic/revolver.dm | 8 +++++--- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/code/modules/projectiles/guns/ballistic.dm b/code/modules/projectiles/guns/ballistic.dm index 35745899543..635071818f0 100644 --- a/code/modules/projectiles/guns/ballistic.dm +++ b/code/modules/projectiles/guns/ballistic.dm @@ -253,7 +253,7 @@ if (chambered && !chambered.BB) chambered.forceMove(drop_location()) chambered = null - var/num_loaded = magazine.attackby(A, user, params, TRUE) + var/num_loaded = magazine?.attackby(A, user, params, TRUE) if (num_loaded) to_chat(user, "You load [num_loaded] [cartridge_wording]\s into \the [src].") playsound(src, load_sound, load_sound_volume, load_sound_vary) @@ -410,7 +410,8 @@ rounds.Add(chambered) if(drop_all) chambered = null - rounds.Add(magazine.ammo_list(drop_all)) + if(magazine) + rounds.Add(magazine.ammo_list(drop_all)) return rounds #define BRAINS_BLOWN_THROW_RANGE 3 diff --git a/code/modules/projectiles/guns/ballistic/revolver.dm b/code/modules/projectiles/guns/ballistic/revolver.dm index 80902b037b8..6defdc6659c 100644 --- a/code/modules/projectiles/guns/ballistic/revolver.dm +++ b/code/modules/projectiles/guns/ballistic/revolver.dm @@ -17,6 +17,8 @@ var/recent_spin = 0 /obj/item/gun/ballistic/revolver/chamber_round(spin_cylinder = TRUE) + if(!magazine) //if it mag was qdel'd somehow. + CRASH("revolver tried to chamber a round without a magazine!") if(spin_cylinder) chambered = magazine.get_round(TRUE) else @@ -91,14 +93,14 @@ ) /obj/item/gun/ballistic/revolver/detective/process_fire(atom/target, mob/living/user, message = TRUE, params = null, zone_override = "", bonus_spread = 0) - if(magazine.caliber != initial(magazine.caliber)) + if(magazine && magazine.caliber != initial(magazine.caliber)) if(prob(70 - (magazine.ammo_count() * 10))) //minimum probability of 10, maximum of 60 playsound(user, fire_sound, fire_sound_volume, vary_fire_sound) to_chat(user, "[src] blows up in your face!") user.take_bodypart_damage(0,20) user.dropItemToGround(src) - return 0 - ..() + return FALSE + return ..() /obj/item/gun/ballistic/revolver/detective/screwdriver_act(mob/living/user, obj/item/I) if(..()) From fd77d2a770c5860ec3a603ee35712b4301c42a46 Mon Sep 17 00:00:00 2001 From: Kathy Date: Wed, 8 Jul 2020 03:47:43 +0200 Subject: [PATCH 041/196] Changes mutation chances --- code/modules/hydroponics/hydroponics.dm | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/code/modules/hydroponics/hydroponics.dm b/code/modules/hydroponics/hydroponics.dm index eccc38e494f..5969eedd826 100644 --- a/code/modules/hydroponics/hydroponics.dm +++ b/code/modules/hydroponics/hydroponics.dm @@ -256,7 +256,8 @@ //This is where stability mutations exist now. if(myseed.instability >= 80) - mutate(0, 0, 0, 0, 0, 0, 0, 5, 0) //Exceedingly low odds of gaining a trait. + var/mutation_chance = 5 + (100 - myseed.instability) + mutate(0, 0, 0, 0, 0, 0, 0, mutation_chance, 0) //Exceedingly low odds of gaining a trait. if(myseed.instability >= 60) if(prob((myseed.instability)/2) && !self_sustaining && length(myseed.mutatelist)) //Minimum 30%, Maximum 50% chance of mutating every age tick when not on autogrow. mutatespecie() From d635d3cf38b51f3017f511e69e1179c3c3b44d1c Mon Sep 17 00:00:00 2001 From: Wayland-Smithy <64715958+Wayland-Smithy@users.noreply.github.com> Date: Tue, 7 Jul 2020 21:58:28 -0400 Subject: [PATCH 042/196] the truth menaces with spines --- code/modules/antagonists/blob/blobstrains/reactive_spines.dm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/modules/antagonists/blob/blobstrains/reactive_spines.dm b/code/modules/antagonists/blob/blobstrains/reactive_spines.dm index dd68ff63ff0..345d767e0e4 100644 --- a/code/modules/antagonists/blob/blobstrains/reactive_spines.dm +++ b/code/modules/antagonists/blob/blobstrains/reactive_spines.dm @@ -2,7 +2,7 @@ /datum/blobstrain/reagent/reactive_spines name = "Reactive Spines" description = "will do medium brute damage through armor and bio resistance." - effectdesc = "will also react when attacked with close up burn or brute damage, attacking all near the attacked blob." + effectdesc = "will also react when attacked with burn or brute damage, attacking everything in melee range." analyzerdescdamage = "Does medium brute damage, ignoring armor and bio resistance." analyzerdesceffect = "When attacked with burn or brute damage it violently lashes out, attacking everything nearby." color = "#9ACD32" From 5230ee09b1ea1c2d73aebc7de837c7e9c143a1d1 Mon Sep 17 00:00:00 2001 From: MrMelbert Date: Wed, 8 Jul 2020 02:46:20 -0500 Subject: [PATCH 043/196] oh yeaaaaah --- code/modules/flufftext/Hallucination.dm | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/code/modules/flufftext/Hallucination.dm b/code/modules/flufftext/Hallucination.dm index e16c061ff8f..0adef27f864 100644 --- a/code/modules/flufftext/Hallucination.dm +++ b/code/modules/flufftext/Hallucination.dm @@ -319,7 +319,11 @@ GLOBAL_LIST_INIT(hallucination_list, list( /datum/hallucination/oh_yeah/proc/bubble_attack(turf/landing) var/charged = FALSE //only get hit once - while(get_turf(bubblegum) != landing && target && target.stat != DEAD) + while(get_turf(bubblegum) != landing && target?.stat != DEAD) + if(!landing) + break + if((get_turf(bubblegum)).loc.z != landing.loc.z) + break bubblegum.forceMove(get_step_towards(bubblegum, landing)) bubblegum.setDir(get_dir(bubblegum, landing)) target.playsound_local(get_turf(bubblegum), 'sound/effects/meteorimpact.ogg', 150, 1) From edc2a3dfb850017310e06dc52b089a414752c064 Mon Sep 17 00:00:00 2001 From: tgstation-server Date: Wed, 8 Jul 2020 01:57:56 -0700 Subject: [PATCH 044/196] Automatic changelog generation for PR #52069 [ci skip] --- html/changelogs/AutoChangeLog-pr-52069.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-52069.yml diff --git a/html/changelogs/AutoChangeLog-pr-52069.yml b/html/changelogs/AutoChangeLog-pr-52069.yml new file mode 100644 index 00000000000..dd275519b06 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-52069.yml @@ -0,0 +1,4 @@ +author: "Wayland-Smithy" +delete-after: True +changes: + - rscadd: "Blob mobs from an overmind now see critical mass progress as a status." From ff4ff6c39b395e5b2cee6c322980488df90d8407 Mon Sep 17 00:00:00 2001 From: Kathy Date: Wed, 8 Jul 2020 11:53:14 +0200 Subject: [PATCH 045/196] I'm stupid --- code/modules/hydroponics/hydroponics.dm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/code/modules/hydroponics/hydroponics.dm b/code/modules/hydroponics/hydroponics.dm index 5969eedd826..5beea890eb0 100644 --- a/code/modules/hydroponics/hydroponics.dm +++ b/code/modules/hydroponics/hydroponics.dm @@ -256,8 +256,8 @@ //This is where stability mutations exist now. if(myseed.instability >= 80) - var/mutation_chance = 5 + (100 - myseed.instability) - mutate(0, 0, 0, 0, 0, 0, 0, mutation_chance, 0) //Exceedingly low odds of gaining a trait. + var/mutation_chance = myseed.instability - 75 + mutate(0, 0, 0, 0, 0, 0, 0, mutation_chance, 0) //Scaling odds of a random trait or chemical if(myseed.instability >= 60) if(prob((myseed.instability)/2) && !self_sustaining && length(myseed.mutatelist)) //Minimum 30%, Maximum 50% chance of mutating every age tick when not on autogrow. mutatespecie() From 6b1b054b8f41e16502f99d5b86fc3b656dee3857 Mon Sep 17 00:00:00 2001 From: tgstation-server Date: Wed, 8 Jul 2020 02:58:36 -0700 Subject: [PATCH 046/196] Automatic changelog generation for PR #51817 [ci skip] --- html/changelogs/AutoChangeLog-pr-51817.yml | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-51817.yml diff --git a/html/changelogs/AutoChangeLog-pr-51817.yml b/html/changelogs/AutoChangeLog-pr-51817.yml new file mode 100644 index 00000000000..f9cd9175c47 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-51817.yml @@ -0,0 +1,6 @@ +author: "Melbert" +delete-after: True +changes: + - rscadd: "Mech vs Mech combat system for toy mechs! Hit a toy mech with another toy mech, or attack a player holding a toy mech with a toy mech, and you can initiate battle between toys!" + - rscadd: "Each toy mech now has its own health stat and special attack move for use in mech vs mech combat. Discover them all!" + - rscadd: "Suicide act for mech toys. If you wanted to take part in the toy mech battles yourself." From 761c1a9e031fd7449f5bf8336a44334b5239a169 Mon Sep 17 00:00:00 2001 From: Timberpoes Date: Wed, 8 Jul 2020 11:18:50 +0100 Subject: [PATCH 047/196] Orbit feex --- code/modules/mob/dead/observer/orbit.dm | 7 +++--- tgui/packages/tgui/interfaces/Orbit.js | 33 ++++++++++++++++--------- 2 files changed, 26 insertions(+), 14 deletions(-) diff --git a/code/modules/mob/dead/observer/orbit.dm b/code/modules/mob/dead/observer/orbit.dm index 137de92db86..b12236a6644 100644 --- a/code/modules/mob/dead/observer/orbit.dm +++ b/code/modules/mob/dead/observer/orbit.dm @@ -16,11 +16,10 @@ return if (action == "orbit") - var/list/pois = getpois(skip_mindless = 1) - var/atom/movable/poi = pois[params["name"]] + var/ref = params["ref"] + var/atom/movable/poi = locate(ref) if (poi != null) owner.ManualFollow(poi) - ui.close() /datum/orbit_menu/ui_data(mob/user) var/list/data = list() @@ -39,6 +38,8 @@ var/poi = pois[name] + serialized["ref"] = REF(poi) + var/mob/M = poi if (istype(M)) if (isobserver(M)) diff --git a/tgui/packages/tgui/interfaces/Orbit.js b/tgui/packages/tgui/interfaces/Orbit.js index 131c63651df..33e1da5c409 100644 --- a/tgui/packages/tgui/interfaces/Orbit.js +++ b/tgui/packages/tgui/interfaces/Orbit.js @@ -1,8 +1,10 @@ import { createSearch } from 'common/string'; -import { Box, Button, Input, Section } from '../components'; +import { Box, Button, Input, Icon, Section, Flex } from '../components'; import { Window } from '../layouts'; import { useBackend, useLocalState } from '../backend'; +import { createLogger } from '../logging'; + const PATTERN_DESCRIPTOR = / \[(?:ghost|dead)\]$/; const PATTERN_NUMBER = / \(([0-9]+)\)$/; @@ -44,7 +46,7 @@ const BasicSection = (props, context) => { key={thing.name} content={thing.name.replace(PATTERN_DESCRIPTOR, "")} onClick={() => act("orbit", { - name: thing.name, + ref: thing.ref, })} /> ))} @@ -59,7 +61,7 @@ const OrbitedButton = (props, context) => {