diff --git a/_maps/RandomZLevels/away_mission/SnowCabin.dmm b/_maps/RandomZLevels/away_mission/SnowCabin.dmm
index 7de2ed53f1..fea4130184 100644
--- a/_maps/RandomZLevels/away_mission/SnowCabin.dmm
+++ b/_maps/RandomZLevels/away_mission/SnowCabin.dmm
@@ -3166,7 +3166,7 @@
},
/area/awaymission/cabin/caves/sovietcave)
"il" = (
-/obj/item/toy/prize/deathripley{
+/obj/item/toy/mecha/deathripley{
desc = "The mining mecha of the exploration team.";
name = "exploraton squad Ripley";
pixel_y = 15
diff --git a/_maps/map_files/CogStation/CogStation.dmm b/_maps/map_files/CogStation/CogStation.dmm
index 5fbbc1d401..fcd4949a27 100644
--- a/_maps/map_files/CogStation/CogStation.dmm
+++ b/_maps/map_files/CogStation/CogStation.dmm
@@ -16856,7 +16856,7 @@
/turf/open/floor/plating,
/area/command/heads_quarters/hos)
"aLb" = (
-/obj/item/toy/prize/honk,
+/obj/item/toy/mecha/honk,
/obj/structure/disposalpipe/segment{
dir = 4
},
diff --git a/_maps/map_files/MetaStation/MetaStation.dmm b/_maps/map_files/MetaStation/MetaStation.dmm
index cd48e318bf..d70bb9d9b4 100644
--- a/_maps/map_files/MetaStation/MetaStation.dmm
+++ b/_maps/map_files/MetaStation/MetaStation.dmm
@@ -47411,7 +47411,7 @@
"dmL" = (
/obj/machinery/light/small,
/obj/item/toy/dummy,
-/obj/item/toy/prize/honk{
+/obj/item/toy/mecha/honk{
pixel_y = 12
},
/obj/structure/table/wood,
diff --git a/_maps/map_files/generic/CentCom.dmm b/_maps/map_files/generic/CentCom.dmm
index e5f5a9c6b7..208bca6705 100644
--- a/_maps/map_files/generic/CentCom.dmm
+++ b/_maps/map_files/generic/CentCom.dmm
@@ -17971,7 +17971,7 @@
/area/syndicate_mothership)
"Sb" = (
/obj/structure/table/wood,
-/obj/item/toy/prize/mauler{
+/obj/item/toy/mecha/mauler{
pixel_y = 12
},
/turf/open/floor/plasteel/dark,
diff --git a/code/datums/elements/series.dm b/code/datums/elements/series.dm
new file mode 100644
index 0000000000..0b34b24ae5
--- /dev/null
+++ b/code/datums/elements/series.dm
@@ -0,0 +1,35 @@
+/**
+ * ## series element!
+ *
+ * bespoke element that assigns a series number to toys on examine, and shows their series name!
+ * used for mechas and rare collectable hats, should totally be used for way more ;)
+ */
+/datum/element/series
+ element_flags = ELEMENT_BESPOKE | ELEMENT_DETACH // Detach for turfs
+ id_arg_index = 2
+ var/list/subtype_list
+ var/series_name
+
+/datum/element/series/Attach(datum/target, subtype, series_name)
+ . = ..()
+ if(!isatom(target))
+ return ELEMENT_INCOMPATIBLE
+ if(!subtype)
+ stack_trace("series element without subtype given!")
+ return ELEMENT_INCOMPATIBLE
+ subtype_list = subtypesof(subtype)
+ src.series_name = series_name
+ var/atom/attached = target
+ RegisterSignal(attached, COMSIG_PARENT_EXAMINE, PROC_REF(on_examine))
+
+/datum/element/series/Detach(datum/target)
+ . = ..()
+ UnregisterSignal(target, COMSIG_PARENT_EXAMINE)
+
+///signal called examining
+/datum/element/series/proc/on_examine(datum/target, mob/user, list/examine_list)
+ SIGNAL_HANDLER
+
+ var/series_number = subtype_list.Find(target.type)
+ examine_list += span_boldnotice("[target] is part of the \"[series_name]\" series!")
+ examine_list += span_notice("Collect them all: [series_number]/[length(subtype_list)].")
diff --git a/code/datums/elements/toy_talk.dm b/code/datums/elements/toy_talk.dm
new file mode 100644
index 0000000000..8061eafaeb
--- /dev/null
+++ b/code/datums/elements/toy_talk.dm
@@ -0,0 +1,28 @@
+/**
+ * Allows people to talk via the item with .l or .r
+ *
+ * Be sure to override [/atom/movable/proc/GetVoice] if you want the item's "voice" to not default to itself
+ */
+/datum/element/toy_talk
+
+/datum/element/toy_talk/Attach(datum/target)
+ . = ..()
+ if(!isitem(target))
+ return ELEMENT_INCOMPATIBLE
+
+ RegisterSignal(target, COMSIG_ITEM_TALK_INTO, PROC_REF(do_talk))
+
+/datum/element/toy_talk/Detach(datum/source, ...)
+ . = ..()
+ UnregisterSignal(source, COMSIG_ITEM_TALK_INTO)
+
+/datum/element/toy_talk/proc/do_talk(obj/item/source, mob/speaker, message, channel, list/spans, language, list/message_mods)
+ SIGNAL_HANDLER
+
+ if(!ismob(speaker) || message_mods[MODE_HEADSET] || message_mods[MODE_RELAY])
+ return NONE
+
+ message_mods[MODE_RELAY] = TRUE // Redundant (given NOPASS) but covers our bases
+ speaker.log_talk(message, LOG_SAY, tag = "toy talk ([source])")
+ source.say(message, language = language, sanitize = FALSE, message_mods = list(MODE_RELAY = TRUE))
+ return NOPASS
diff --git a/code/game/machinery/computer/arcade/orion_trail.dm b/code/game/machinery/computer/arcade/orion_trail.dm
index 8894d53a21..21481b3755 100644
--- a/code/game/machinery/computer/arcade/orion_trail.dm
+++ b/code/game/machinery/computer/arcade/orion_trail.dm
@@ -869,7 +869,7 @@
/obj/item/orion_ship
name = "model settler ship"
desc = "A model spaceship, it looks like those used back in the day when travelling to Orion! It even has a miniature FX-293 reactor, which was renowned for its instability and tendency to explode..."
- icon = 'icons/obj/toy.dmi'
+ icon = 'icons/obj/toys/toy.dmi'
icon_state = "ship"
w_class = WEIGHT_CLASS_SMALL
var/active = 0 //if the ship is on
diff --git a/code/game/objects/items/AI_modules.dm b/code/game/objects/items/AI_modules.dm
index 7492f5779f..1906f85a6f 100644
--- a/code/game/objects/items/AI_modules.dm
+++ b/code/game/objects/items/AI_modules.dm
@@ -525,7 +525,7 @@ AI MODULES
/obj/item/ai_module/toyAI // -- Incoming //No actual reason to inherit from ion boards here, either. *sigh* ~Miauw
name = "toy AI"
desc = "A little toy model AI core with real law uploading action!" //Note: subtle tell
- icon = 'icons/obj/toy.dmi'
+ icon = 'icons/obj/toys/toy.dmi'
icon_state = "AI"
laws = list("")
diff --git a/code/game/objects/items/eightball.dm b/code/game/objects/items/eightball.dm
index ff15511092..a189c1a9ba 100644
--- a/code/game/objects/items/eightball.dm
+++ b/code/game/objects/items/eightball.dm
@@ -2,7 +2,7 @@
name = "magic eightball"
desc = "A black ball with a stenciled number eight in white on the side. It seems full of dark liquid.\nThe instructions state that you should ask your question aloud, and then shake."
- icon = 'icons/obj/toy.dmi'
+ icon = 'icons/obj/toys/toy.dmi'
icon_state = "eightball"
w_class = WEIGHT_CLASS_TINY
diff --git a/code/game/objects/items/storage/boxes.dm b/code/game/objects/items/storage/boxes.dm
index a935a55ecf..1fed9a6d4c 100644
--- a/code/game/objects/items/storage/boxes.dm
+++ b/code/game/objects/items/storage/boxes.dm
@@ -621,7 +621,7 @@
/obj/item/storage/box/snappops
name = "snap pop box"
desc = "Eight wrappers of fun! Ages 8 and up. Not suitable for children."
- icon = 'icons/obj/toy.dmi'
+ icon = 'icons/obj/toys/toy.dmi'
icon_state = "spbox"
illustration = null
@@ -870,7 +870,7 @@
/obj/item/storage/box/mechfigures/PopulateContents()
for(var/i in 1 to 4)
- var/randomFigure = pick(subtypesof(/obj/item/toy/prize/))
+ var/randomFigure = pick(subtypesof(/obj/item/toy/mecha))
new randomFigure(src)
diff --git a/code/game/objects/items/toy_mechs.dm b/code/game/objects/items/toy_mechs.dm
new file mode 100644
index 0000000000..efcd7862ef
--- /dev/null
+++ b/code/game/objects/items/toy_mechs.dm
@@ -0,0 +1,654 @@
+/**
+ * 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/mecha
+ icon = 'icons/obj/toys/toy.dmi'
+ icon_state = "fivestarstoy"
+ 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
+ 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
+ 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/mecha/Initialize(mapload)
+ . = ..()
+ AddElement(/datum/element/series, /obj/item/toy/mecha, "Mini-Mecha action figures")
+ //AddElement(/datum/element/toy_talk)
+ 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/mecha/proc/combat_sleep(delay, obj/item/toy/mecha/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(span_suicide("[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(span_notice("[attacker] and [src] separate, ending the battle."), \
+ span_notice("[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(/datum/mutation/human/telekinesis)))
+ attacker_controller.visible_message(span_notice("[attacker_controller.name] separates from [attacker], ending the battle."), \
+ span_notice("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(/datum/mutation/human/telekinesis)))
+ opponent.visible_message(span_notice("[opponent.name] separates from [src], ending the battle."), \
+ span_notice("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(/datum/mutation/human/telekinesis)))
+ attacker_controller.visible_message(span_notice("[attacker_controller.name] separates from [src] and [attacker], ending the battle."), \
+ span_notice("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/mecha/attack_self(mob/user)
+ if(timer < world.time)
+ to_chat(user, span_notice("You play with [src]."))
+ timer = world.time + cooldown
+ if(!quiet)
+ playsound(user, 'sound/mecha/mechstep.ogg', 20, TRUE)
+ else
+ . = ..()
+
+/obj/item/toy/mecha/attack_hand(mob/user, list/modifiers)
+ . = ..()
+ if(.)
+ return
+ if(loc == user)
+ attack_self(user)
+
+/**
+ * If you attack a mech with a mech, initiate combat between them
+ */
+/obj/item/toy/mecha/attackby(obj/item/user_toy, mob/living/user)
+ if(istype(user_toy, /obj/item/toy/mecha))
+ var/obj/item/toy/mecha/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/mecha/attack(mob/living/carbon/human/target, mob/living/carbon/human/user)
+ if(target == user)
+ to_chat(user, span_notice("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, span_notice("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/mecha)) //if you attack someone with a mech who's also holding a mech, offer to battle them
+ var/obj/item/toy/mecha/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, span_notice("You offer battle to [target.name]!"))
+ to_chat(target, span_notice("[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_REF(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/mecha/attack_tk(mob/user)
+ if(timer < world.time)
+ to_chat(user, span_notice("You telekinetically play with [src]."))
+ timer = world.time + cooldown
+ if(!quiet)
+ playsound(user, 'sound/mecha/mechstep.ogg', 20, TRUE)
+ return STOP_ATTACK_PROC_CHAIN
+
+
+/**
+ * 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/mecha/proc/withdraw_offer(mob/living/carbon/user)
+ if(wants_to_battle)
+ wants_to_battle = FALSE
+ to_chat(user, span_notice("You get the feeling they don't want to battle."))
+/**
+ * Starts a battle, toy mech vs player. Player... doesn't win.
+ */
+/obj/item/toy/mecha/suicide_act(mob/living/carbon/user)
+ if(in_combat)
+ to_chat(user, span_notice("[src] is in battle, let it finish first."))
+ return
+
+ user.visible_message(span_suicide("[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/mecha/examine()
+ . = ..()
+ . += span_notice("This toy's special attack is [special_attack_cry], [special_attack_type_message]")
+ if(in_combat)
+ . += span_notice("This toy has a maximum health of [max_combat_health]. Currently, it's [combat_health].")
+ . += span_notice("Its special move light is [special_attack_cooldown? "flashing red." : "green and is ready!"]")
+ else
+ . += span_notice("This toy has a maximum health of [max_combat_health].")
+
+ if(wins || losses)
+ . += span_notice("This toy has [wins] wins, and [losses] losses.")
+
+/obj/item/toy/mecha/can_speak(allow_mimes)
+ return !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/mecha/proc/mecha_brawl(obj/item/toy/mecha/attacker, mob/living/carbon/attacker_controller, mob/living/carbon/opponent)
+ //A GOOD DAY FOR A SWELL BATTLE!
+ attacker_controller.visible_message(span_danger("[attacker_controller.name] collides [attacker] with [src]! Looks like they're preparing for a brawl!"), \
+ span_danger("You collide [attacker] into [src], sparking a fierce battle!"), \
+ span_hear("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*cooldown_multiplier
+ attacker.timer = world.time + attacker.cooldown*attacker.cooldown_multiplier
+
+ 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(span_danger("[src] unleashes its special attack!!"), \
+ span_danger("You unleash [src]'s special attack!"))
+ special_attack_move(attacker)
+ else if(attacker.special_attack_charged)
+
+ attacker_controller.visible_message(span_danger("[attacker] unleashes its special attack!!"), \
+ span_danger("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(span_danger("[attacker] begins charging its special attack!!"), \
+ span_danger("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(span_danger("[attacker] devastates [src]!"), \
+ span_danger("You ram [attacker] into [src]!"), \
+ span_hear("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(span_boldwarning("...and lands a CRIPPLING BLOW!"), \
+ span_boldwarning("...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(span_danger("[attacker] and [src] clash dramatically, causing sparks to fly!"), \
+ span_danger("[attacker] and [src] clash dramatically, causing sparks to fly!"), \
+ span_hear("You hear hard plastic rubbing against hard plastic."), COMBAT_MESSAGE_RANGE)
+ else
+ src_controller.visible_message(span_danger("[src] and [attacker] clash dramatically, causing sparks to fly!"), \
+ span_danger("[src] and [attacker] clash dramatically, causing sparks to fly!"), \
+ span_hear("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(span_danger("[src]'s attack deflects off of [attacker]."), \
+ span_danger("[src]'s attack deflects off of [attacker]."), \
+ span_hear("You hear hard plastic bouncing off hard plastic."), COMBAT_MESSAGE_RANGE)
+ else
+ src_controller.visible_message(span_danger("[attacker]'s attack deflects off of [src]."), \
+ span_danger("[attacker]'s attack deflects off of [src]."), \
+ span_hear("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(span_danger("[src] begins charging its special attack!!"), \
+ span_danger("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(span_danger("[src] smashes [attacker]!"), \
+ span_danger("You smash [src] into [attacker]!"), \
+ span_hear("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(span_boldwarning("...and lands a CRIPPLING BLOW!"), \
+ span_boldwarning("...and you land a CRIPPLING blow on [attacker]!"), null, COMBAT_MESSAGE_RANGE)
+ else
+ attacker_controller.visible_message(span_notice("[src] and [attacker] stand around awkwardly."), \
+ span_notice("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(span_boldnotice("MUTUALLY ASSURED DESTRUCTION!! [src] and [attacker] both end up destroyed!"), \
+ span_boldnotice("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(span_notice("[attacker] falls apart!"), \
+ span_notice("[attacker] falls apart!"), null, COMBAT_MESSAGE_RANGE)
+ say("[pick(winlines)]")
+ src_controller.visible_message(span_notice("[src] destroys [attacker] and walks away victorious!"), \
+ span_notice("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(span_notice("[src] collapses!"), \
+ span_notice("[src] collapses!"), null, COMBAT_MESSAGE_RANGE)
+ attacker.say("[pick(winlines)]")
+ attacker_controller.visible_message(span_notice("[attacker] demolishes [src] and walks away victorious!"), \
+ "[span_notice("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/mecha/proc/check_battle_start(mob/living/carbon/user, obj/item/toy/mecha/attacker, mob/living/carbon/target)
+ if(attacker?.in_combat)
+ to_chat(user, span_notice("[target?target.p_their() : "Your" ] [attacker.name] is in combat."))
+ to_chat(target, span_notice("Your [attacker.name] is in combat."))
+ return FALSE
+ if(in_combat)
+ to_chat(user, span_notice("Your [name] is in combat."))
+ to_chat(target, span_notice("[user.p_their()] [name] is in combat."))
+ return FALSE
+ if(attacker && attacker.timer > world.time)
+ to_chat(user, span_notice("[target?target.p_their() : "Your" ] [attacker.name] isn't ready for battle."))
+ to_chat(target, span_notice("Your [attacker.name] isn't ready for battle."))
+ return FALSE
+ if(timer > world.time)
+ to_chat(user, span_notice("Your [name] isn't ready for battle."))
+ to_chat(target, span_notice("[user.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/mecha/proc/special_attack_move(obj/item/toy/mecha/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/mecha/proc/super_special_attack(obj/item/toy/mecha/victim)
+ visible_message(span_notice("[src] does a cool flip."))
+
+/obj/item/toy/mecha/ripley
+ name = "toy Ripley MK-I"
+ icon_state = "ripleytoy"
+ max_combat_health = 4 //200 integrity
+ special_attack_type = SPECIAL_ATTACK_DAMAGE
+ special_attack_cry = "CLAMP SMASH"
+
+/obj/item/toy/mecha/ripleymkii
+ name = "toy Ripley MK-II"
+ icon_state = "ripleymkiitoy"
+ max_combat_health = 5 //250 integrity
+ special_attack_type = SPECIAL_ATTACK_DAMAGE
+ special_attack_cry = "GIGA DRILL BREAK"
+
+/obj/item/toy/mecha/hauler
+ name = "toy Hauler"
+ icon_state = "haulertoy"
+ max_combat_health = 3 //100 integrity?
+ special_attack_type = SPECIAL_ATTACK_UTILITY
+ special_attack_cry = "HAUL AWAY"
+
+/obj/item/toy/mecha/clarke
+ name = "toy Clarke"
+ icon_state = "clarketoy"
+ max_combat_health = 4 //200 integrity
+ special_attack_type = SPECIAL_ATTACK_UTILITY
+ special_attack_cry = "ROLL OUT"
+
+/obj/item/toy/mecha/odysseus
+ name = "toy Odysseus"
+ icon_state = "odysseustoy"
+ max_combat_health = 4 //120 integrity
+ special_attack_type = SPECIAL_ATTACK_HEAL
+ special_attack_cry = "MECHA BEAM"
+
+/obj/item/toy/mecha/gygax
+ name = "toy Gygax"
+ icon_state = "gygaxtoy"
+ max_combat_health = 5 //250 integrity
+ special_attack_type = SPECIAL_ATTACK_UTILITY
+ special_attack_cry = "SUPER SERVOS"
+
+/obj/item/toy/mecha/durand
+ name = "toy Durand"
+ icon_state = "durandtoy"
+ max_combat_health = 6 //400 integrity
+ special_attack_type = SPECIAL_ATTACK_HEAL
+ special_attack_cry = "SHIELD OF PROTECTION"
+
+/obj/item/toy/mecha/savannahivanov
+ name = "toy Savannah-Ivanov"
+ icon_state = "savannahivanovtoy"
+ max_combat_health = 7 //450 integrity
+ special_attack_type = SPECIAL_ATTACK_UTILITY
+ special_attack_cry = "SKYFALL!! IVANOV STRIKE"
+
+/obj/item/toy/mecha/phazon
+ name = "toy Phazon"
+ icon_state = "phazontoy"
+ max_combat_health = 6 //200 integrity
+ special_attack_type = SPECIAL_ATTACK_UTILITY
+ special_attack_cry = "NO-CLIP"
+
+/obj/item/toy/mecha/honk
+ name = "toy H.O.N.K."
+ icon_state = "honktoy"
+ 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/mecha/honk/super_special_attack(obj/item/toy/mecha/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/mecha/darkgygax
+ name = "toy Dark Gygax"
+ icon_state = "darkgygaxtoy"
+ max_combat_health = 6 //300 integrity
+ special_attack_type = SPECIAL_ATTACK_UTILITY
+ special_attack_cry = "ULTRA SERVOS"
+
+/obj/item/toy/mecha/mauler
+ name = "toy Mauler"
+ icon_state = "maulertoy"
+ max_combat_health = 7 //500 integrity
+ special_attack_type = SPECIAL_ATTACK_DAMAGE
+ special_attack_cry = "BULLET STORM"
+
+/obj/item/toy/mecha/darkhonk
+ name = "toy Dark H.O.N.K."
+ icon_state = "darkhonktoy"
+ max_combat_health = 5 //300 integrity
+ special_attack_type = SPECIAL_ATTACK_DAMAGE
+ special_attack_cry = "BOMBANANA SPREE"
+
+/obj/item/toy/mecha/deathripley
+ name = "toy Death-Ripley"
+ icon_state = "deathripleytoy"
+ 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"
+
+/obj/item/toy/mecha/deathripley/super_special_attack(obj/item/toy/mecha/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/mecha/reticence
+ name = "toy Reticence"
+ icon_state = "reticencetoy"
+ quiet = TRUE
+ 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"
+
+/obj/item/toy/mecha/reticence/super_special_attack(obj/item/toy/mecha/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/mecha/marauder
+ name = "toy Marauder"
+ icon_state = "maraudertoy"
+ max_combat_health = 7 //500 integrity
+ special_attack_type = SPECIAL_ATTACK_DAMAGE
+ special_attack_cry = "BEAM BLAST"
+
+/obj/item/toy/mecha/seraph
+ name = "toy Seraph"
+ icon_state = "seraphtoy"
+ max_combat_health = 8 //550 integrity
+ special_attack_type = SPECIAL_ATTACK_DAMAGE
+ special_attack_cry = "ROCKET BARRAGE"
+
+/obj/item/toy/mecha/firefighter //rip
+ name = "toy Firefighter"
+ icon_state = "firefightertoy"
+ max_combat_health = 5 //250 integrity?
+ special_attack_type = SPECIAL_ATTACK_HEAL
+ special_attack_cry = "FIRE SHIELD"
+
+#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 b05abdc49a..3ea05d3fbd 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
@@ -24,7 +23,6 @@
* Toy Daggers
*/
-
/obj/item/toy
throwforce = 0
throw_speed = 3
@@ -39,7 +37,7 @@
/obj/item/toy/balloon
name = "water balloon"
desc = "A translucent balloon. There's nothing in it."
- icon = 'icons/obj/toy.dmi'
+ icon = 'icons/obj/toys/toy.dmi'
icon_state = "waterballoon-e"
item_state = "balloon-empty"
@@ -379,7 +377,7 @@
/obj/item/toy/foamblade
name = "foam armblade"
desc = "It says \"Sternside Changs #1 fan\" on it."
- icon = 'icons/obj/toy.dmi'
+ icon = 'icons/obj/toys/toy.dmi'
icon_state = "foamblade"
item_state = "arm_blade"
lefthand_file = 'icons/mob/inhands/antag/changeling_lefthand.dmi'
@@ -508,7 +506,7 @@
/obj/item/toy/snappop
name = "snap pop"
desc = "Wow!"
- icon = 'icons/obj/toy.dmi'
+ icon = 'icons/obj/toys/toy.dmi'
icon_state = "snappop"
w_class = WEIGHT_CLASS_TINY
var/ash_type = /obj/effect/decal/cleanable/ash
@@ -544,7 +542,7 @@
ash_type = /obj/effect/decal/cleanable/ash/snappop_phoenix
/obj/effect/decal/cleanable/ash/snappop_phoenix
- var/respawn_time = 300
+ var/respawn_time = 30 SECONDS
/obj/effect/decal/cleanable/ash/snappop_phoenix/New()
. = ..()
@@ -554,96 +552,10 @@
new /obj/item/toy/snappop/phoenix(get_turf(src))
qdel(src)
-
-/*
- * Mech prizes
- */
-/obj/item/toy/prize
- icon = 'icons/obj/toy.dmi'
- icon_state = "ripleytoy"
- var/timer = 0
- var/cooldown = 30
- var/quiet = 0
-
-//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, 1)
- else
- . = ..()
-
-/obj/item/toy/prize/on_attack_hand(mob/user, act_intent = user.a_intent, unarmed_attack_flags)
- if(loc == user)
- attack_self(user)
-
-/obj/item/toy/prize/ripley
- name = "toy Ripley"
- desc = "Mini-Mecha action figure! Collect them all! 1/12."
-
-/obj/item/toy/prize/fireripley
- name = "toy firefighting Ripley"
- desc = "Mini-Mecha action figure! Collect them all! 2/12."
- icon_state = "fireripleytoy"
-
-/obj/item/toy/prize/deathripley
- name = "toy deathsquad Ripley"
- desc = "Mini-Mecha action figure! Collect them all! 3/12."
- icon_state = "deathripleytoy"
-
-/obj/item/toy/prize/gygax
- name = "toy Gygax"
- desc = "Mini-Mecha action figure! Collect them all! 4/12."
- icon_state = "gygaxtoy"
-
-/obj/item/toy/prize/durand
- name = "toy Durand"
- desc = "Mini-Mecha action figure! Collect them all! 5/12."
- icon_state = "durandprize"
-
-/obj/item/toy/prize/honk
- name = "toy H.O.N.K."
- desc = "Mini-Mecha action figure! Collect them all! 6/12."
- icon_state = "honkprize"
-
-/obj/item/toy/prize/marauder
- name = "toy Marauder"
- desc = "Mini-Mecha action figure! Collect them all! 7/12."
- icon_state = "marauderprize"
-
-/obj/item/toy/prize/seraph
- name = "toy Seraph"
- desc = "Mini-Mecha action figure! Collect them all! 8/12."
- icon_state = "seraphprize"
-
-/obj/item/toy/prize/mauler
- name = "toy Mauler"
- desc = "Mini-Mecha action figure! Collect them all! 9/12."
- icon_state = "maulerprize"
-
-/obj/item/toy/prize/odysseus
- name = "toy Odysseus"
- desc = "Mini-Mecha action figure! Collect them all! 10/12."
- icon_state = "odysseusprize"
-
-/obj/item/toy/prize/phazon
- name = "toy Phazon"
- desc = "Mini-Mecha action figure! Collect them all! 11/12."
- icon_state = "phazonprize"
-
-/obj/item/toy/prize/reticence
- name = "toy Reticence"
- desc = "Mini-Mecha action figure! Collect them all! 12/12."
- icon_state = "reticenceprize"
- quiet = 1
-
-
/obj/item/toy/talking
name = "talking action figure"
desc = "A generic action figure modeled after nothing in particular."
- icon = 'icons/obj/toy.dmi'
+ icon = 'icons/obj/toys/toy.dmi'
icon_state = "owlprize"
w_class = WEIGHT_CLASS_SMALL
var/cooldown = FALSE
@@ -772,7 +684,7 @@
/obj/item/toy/cards/deck
name = "deck of cards"
desc = "A deck of space-grade playing cards."
- icon = 'icons/obj/toy.dmi'
+ icon = 'icons/obj/toys/toy.dmi'
deckstyle = "nanotrasen"
icon_state = "deck_nanotrasen_full"
w_class = WEIGHT_CLASS_SMALL
@@ -903,7 +815,7 @@
/obj/item/toy/cards/cardhand
name = "hand of cards"
desc = "A number of cards not in a deck, customarily held in ones hand."
- icon = 'icons/obj/toy.dmi'
+ icon = 'icons/obj/toys/toy.dmi'
icon_state = "none"
w_class = WEIGHT_CLASS_TINY
var/list/currenthand = list()
@@ -1000,7 +912,7 @@
/obj/item/toy/cards/singlecard
name = "card"
desc = "a card"
- icon = 'icons/obj/toy.dmi'
+ icon = 'icons/obj/toys/toy.dmi'
icon_state = "singlecard_down_nanotrasen"
w_class = WEIGHT_CLASS_TINY
var/cardname = null
@@ -1124,7 +1036,7 @@
/obj/item/toy/nuke
name = "\improper Nuclear Fission Explosive toy"
desc = "A plastic model of a Nuclear Fission Explosive."
- icon = 'icons/obj/toy.dmi'
+ icon = 'icons/obj/toys/toy.dmi'
icon_state = "nuketoyidle"
w_class = WEIGHT_CLASS_SMALL
var/cooldown = 0
@@ -1151,7 +1063,7 @@
/obj/item/toy/minimeteor
name = "\improper Mini-Meteor"
desc = "Relive the excitement of a meteor shower! SweetMeat-eor. Co is not responsible for any injuries, headaches or hearing loss caused by Mini-Meteor."
- icon = 'icons/obj/toy.dmi'
+ icon = 'icons/obj/toys/toy.dmi'
icon_state = "minimeteor"
w_class = WEIGHT_CLASS_SMALL
@@ -1194,7 +1106,7 @@
/obj/item/toy/snowball
name = "snowball"
desc = "A compact ball of snow. Good for throwing at people."
- icon = 'icons/obj/toy.dmi'
+ icon = 'icons/obj/toys/toy.dmi'
icon_state = "snowball"
throwforce = 12 //pelt your enemies to death with lumps of snow
damtype = STAMINA
@@ -1268,7 +1180,7 @@
*/
/obj/item/toy/toy_xeno
- icon = 'icons/obj/toy.dmi'
+ icon = 'icons/obj/toys/toy.dmi'
icon_state = "toy_xeno"
name = "xenomorph action figure"
desc = "MEGA presents the new Xenos Isolated action figure! Comes complete with realistic sounds! Pull back string to use."
@@ -1297,7 +1209,7 @@
/obj/item/toy/cattoy
name = "toy mouse"
desc = "A colorful toy mouse!"
- icon = 'icons/obj/toy.dmi'
+ icon = 'icons/obj/toys/toy.dmi'
icon_state = "toy_mouse"
w_class = WEIGHT_CLASS_SMALL
var/cooldown = 0
@@ -1311,7 +1223,7 @@
/obj/item/toy/figure
name = "Non-Specific Action Figure action figure"
desc = null
- icon = 'icons/obj/toy.dmi'
+ icon = 'icons/obj/toys/toy.dmi'
icon_state = "nuketoy"
var/cooldown = 0
var/toysay = "What the fuck did you do?"
@@ -1518,7 +1430,7 @@
/obj/item/toy/dummy
name = "ventriloquist dummy"
desc = "It's a dummy, dummy."
- icon = 'icons/obj/toy.dmi'
+ icon = 'icons/obj/toys/toy.dmi'
icon_state = "assistant"
item_state = "doll"
var/doll_name = "Dummy"
diff --git a/code/modules/cargo/packs/costumes_toys.dm b/code/modules/cargo/packs/costumes_toys.dm
index 6a37ef80a9..542ba29d14 100644
--- a/code/modules/cargo/packs/costumes_toys.dm
+++ b/code/modules/cargo/packs/costumes_toys.dm
@@ -150,24 +150,30 @@
/obj/item/toy/talking/AI,
/obj/item/toy/talking/codex_gigas,
/obj/item/clothing/under/syndicate/tacticool,
- /obj/item/toy/sword ,
+ /obj/item/toy/sword,
/obj/item/toy/gun,
/obj/item/gun/ballistic/shotgun/toy/crossbow,
/obj/item/storage/box/fakesyndiesuit,
/obj/item/storage/crayons,
/obj/item/toy/spinningtoy,
- /obj/item/toy/prize/ripley,
- /obj/item/toy/prize/fireripley,
- /obj/item/toy/prize/deathripley,
- /obj/item/toy/prize/gygax,
- /obj/item/toy/prize/durand,
- /obj/item/toy/prize/honk,
- /obj/item/toy/prize/marauder,
- /obj/item/toy/prize/seraph,
- /obj/item/toy/prize/mauler,
- /obj/item/toy/prize/odysseus,
- /obj/item/toy/prize/phazon,
- /obj/item/toy/prize/reticence,
+ /obj/item/toy/mecha/ripley,
+ /obj/item/toy/mecha/ripleymkii,
+ /obj/item/toy/mecha/hauler,
+ /obj/item/toy/mecha/clarke,
+ /obj/item/toy/mecha/odysseus,
+ /obj/item/toy/mecha/gygax,
+ /obj/item/toy/mecha/durand,
+ /obj/item/toy/mecha/savannahivanov,
+ /obj/item/toy/mecha/phazon,
+ /obj/item/toy/mecha/honk,
+ /obj/item/toy/mecha/darkgygax,
+ /obj/item/toy/mecha/mauler,
+ /obj/item/toy/mecha/darkhonk,
+ /obj/item/toy/mecha/deathripley,
+ /obj/item/toy/mecha/reticence,
+ /obj/item/toy/mecha/marauder,
+ /obj/item/toy/mecha/seraph,
+ /obj/item/toy/mecha/firefighter,
/obj/item/toy/cards/deck,
/obj/item/toy/nuke,
/obj/item/toy/minimeteor,
diff --git a/code/modules/clothing/head/collectable.dm b/code/modules/clothing/head/collectable.dm
index 20cb7cc824..5c63905d5c 100644
--- a/code/modules/clothing/head/collectable.dm
+++ b/code/modules/clothing/head/collectable.dm
@@ -4,6 +4,11 @@
/obj/item/clothing/head/collectable
name = "collectable hat"
desc = "A rare collectable hat."
+ icon_state = null
+
+/obj/item/clothing/head/collectable/Initialize()
+ . = ..()
+ AddElement(/datum/element/series, /obj/item/clothing/head/collectable, "Super duper collectable hats")
/obj/item/clothing/head/collectable/petehat
name = "ultra rare Pete's hat!"
diff --git a/code/modules/events/holiday/vday.dm b/code/modules/events/holiday/vday.dm
index 16d51337f1..e1a81d6ea6 100644
--- a/code/modules/events/holiday/vday.dm
+++ b/code/modules/events/holiday/vday.dm
@@ -40,7 +40,7 @@
/obj/item/valentine
name = "valentine"
desc = "A Valentine's card! Wonder what it says..."
- icon = 'icons/obj/toy.dmi'
+ icon = 'icons/obj/toys/toy.dmi'
icon_state = "sc_Ace of Hearts_syndicate" // shut up
var/message = "A generic message of love or whatever."
resistance_flags = FLAMMABLE
diff --git a/code/modules/games/cas.dm b/code/modules/games/cas.dm
index 77db8dbe3f..5261887b73 100644
--- a/code/modules/games/cas.dm
+++ b/code/modules/games/cas.dm
@@ -12,7 +12,7 @@
/obj/item/toy/cards/deck/cas
name = "\improper CAS deck (white)"
desc = "A deck for the game Cards Against Spess, still popular after all these centuries. Warning: may include traces of broken fourth wall. This is the white deck."
- icon = 'icons/obj/toy.dmi'
+ icon = 'icons/obj/toys/toy.dmi'
icon_state = "deck_caswhite_full"
deckstyle = "caswhite"
var/card_face = "cas_white"
diff --git a/code/modules/games/unum.dm b/code/modules/games/unum.dm
index 9820fa6754..3cd043e050 100644
--- a/code/modules/games/unum.dm
+++ b/code/modules/games/unum.dm
@@ -2,7 +2,7 @@
/obj/item/toy/cards/deck/unum
name = "\improper UNUM deck"
desc = "A deck of unum cards. House rules to argue over not included."
- icon = 'icons/obj/toy.dmi'
+ icon = 'icons/obj/toys/toy.dmi'
icon_state = "deck_unum_full"
deckstyle = "unum"
original_size = 108
diff --git a/code/modules/holiday/easter.dm b/code/modules/holiday/easter.dm
index 9c538f4bde..89b1944ae0 100644
--- a/code/modules/holiday/easter.dm
+++ b/code/modules/holiday/easter.dm
@@ -118,7 +118,7 @@
var/eggcolor = pick("blue","green","mime","orange","purple","rainbow","red","yellow")
icon_state = "egg-[eggcolor]"
/obj/item/reagent_containers/food/snacks/egg/proc/dispensePrize(turf/where)
- var/won = pick(/obj/item/clothing/head/bunnyhead,
+ var/prize_list = list(/obj/item/clothing/head/bunnyhead,
/obj/item/clothing/suit/bunnysuit,
/obj/item/reagent_containers/food/snacks/grown/carrot,
/obj/item/reagent_containers/food/snacks/chocolateegg,
@@ -126,13 +126,12 @@
/obj/item/toy/gun,
/obj/item/toy/sword,
/obj/item/toy/foamblade,
- /obj/item/toy/prize/ripley,
- /obj/item/toy/prize/honk,
/obj/item/toy/plush/carpplushie,
/obj/item/toy/redbutton,
- /obj/item/clothing/head/collectable/rabbitears)
+ /obj/item/clothing/head/collectable/rabbitears) + subtypesof(/obj/item/toy/mecha)
+ var/won = pick(prize_list)
new won(where)
- new/obj/item/reagent_containers/food/snacks/chocolateegg(where)
+ new /obj/item/reagent_containers/food/snacks/chocolateegg(where)
/obj/item/reagent_containers/food/snacks/egg/attack_self(mob/user)
..()
diff --git a/code/modules/holodeck/holo_effect.dm b/code/modules/holodeck/holo_effect.dm
index 5d6259d9c2..6585ca39a5 100644
--- a/code/modules/holodeck/holo_effect.dm
+++ b/code/modules/holodeck/holo_effect.dm
@@ -26,7 +26,7 @@
// Generates a holodeck-tracked card deck
/obj/effect/holodeck_effect/cards
- icon = 'icons/obj/toy.dmi'
+ icon = 'icons/obj/toys/toy.dmi'
icon_state = "deck_nanotrasen_full"
var/obj/item/toy/cards/deck/D
diff --git a/code/modules/jobs/job_types/roboticist.dm b/code/modules/jobs/job_types/roboticist.dm
index 097023e643..906285a5c8 100644
--- a/code/modules/jobs/job_types/roboticist.dm
+++ b/code/modules/jobs/job_types/roboticist.dm
@@ -27,7 +27,7 @@
threat = 1
family_heirlooms = list(
- /obj/item/toy/figure/borg
+ /obj/item/toy/figure/borg,
)
mail_goodies = list(
@@ -36,6 +36,10 @@
/obj/item/modular_computer/tablet/preset/advanced = 5
)
+/datum/job/roboticist/New()
+ . = ..()
+ family_heirlooms += subtypesof(/obj/item/toy/mecha)
+
/datum/outfit/job/roboticist
name = "Roboticist"
jobtype = /datum/job/roboticist
diff --git a/code/modules/mining/abandoned_crates.dm b/code/modules/mining/abandoned_crates.dm
index 3f213eb934..24b5ce4052 100644
--- a/code/modules/mining/abandoned_crates.dm
+++ b/code/modules/mining/abandoned_crates.dm
@@ -61,7 +61,7 @@
if(53 to 54)
new /obj/item/toy/balloon(src)
if(55 to 56)
- var/newitem = pick(subtypesof(/obj/item/toy/prize))
+ var/newitem = pick(subtypesof(/obj/item/toy/mecha))
new newitem(src)
if(57 to 58)
new /obj/item/toy/syndicateballoon(src)
diff --git a/code/modules/mob/living/simple_animal/guardian/guardian.dm b/code/modules/mob/living/simple_animal/guardian/guardian.dm
index 4c8872ffbf..6170dfdb68 100644
--- a/code/modules/mob/living/simple_animal/guardian/guardian.dm
+++ b/code/modules/mob/living/simple_animal/guardian/guardian.dm
@@ -512,7 +512,7 @@ GLOBAL_LIST_EMPTY(parasites) //all currently existing/living guardians
/obj/item/guardiancreator
name = "deck of tarot cards"
desc = "An enchanted deck of tarot cards, rumored to be a source of unimaginable power."
- icon = 'icons/obj/toy.dmi'
+ icon = 'icons/obj/toys/toy.dmi'
icon_state = "deck_syndicate_full"
var/used = FALSE
var/theme = "magic"
diff --git a/code/modules/pool/pool_noodles.dm b/code/modules/pool/pool_noodles.dm
index 6118354792..6ecb6b30b1 100644
--- a/code/modules/pool/pool_noodles.dm
+++ b/code/modules/pool/pool_noodles.dm
@@ -1,7 +1,7 @@
//Pool noodles
/obj/item/toy/poolnoodle
- icon = 'icons/obj/toy.dmi'
+ icon = 'icons/obj/toys/toy.dmi'
icon_state = "noodle"
name = "pool noodle"
desc = "A strange, bulky, bendable toy that can annoy people."
diff --git a/code/modules/projectiles/guns/ballistic/toy.dm b/code/modules/projectiles/guns/ballistic/toy.dm
index cd3473f8fc..238e5770c4 100644
--- a/code/modules/projectiles/guns/ballistic/toy.dm
+++ b/code/modules/projectiles/guns/ballistic/toy.dm
@@ -69,7 +69,7 @@
/obj/item/gun/ballistic/shotgun/toy/crossbow
name = "foam force crossbow"
desc = "A weapon favored by many overactive children. Ages 8 and up."
- icon = 'icons/obj/toy.dmi'
+ icon = 'icons/obj/toys/toy.dmi'
icon_state = "foamcrossbow"
item_state = "crossbow"
mag_type = /obj/item/ammo_box/magazine/internal/shot/toy/crossbow
diff --git a/code/modules/reagents/reagent_containers/rags.dm b/code/modules/reagents/reagent_containers/rags.dm
index 4982571a3b..6273088453 100644
--- a/code/modules/reagents/reagent_containers/rags.dm
+++ b/code/modules/reagents/reagent_containers/rags.dm
@@ -2,7 +2,7 @@
name = "damp rag"
desc = "For cleaning up messes, you suppose."
w_class = WEIGHT_CLASS_TINY
- icon = 'icons/obj/toy.dmi'
+ icon = 'icons/obj/toys/toy.dmi'
icon_state = "rag"
item_flags = NOBLUDGEON
reagent_flags = REFILLABLE | DRAINABLE
diff --git a/code/modules/research/designs/autoylathe_designs.dm b/code/modules/research/designs/autoylathe_designs.dm
index 6db9755585..95eb469102 100644
--- a/code/modules/research/designs/autoylathe_designs.dm
+++ b/code/modules/research/designs/autoylathe_designs.dm
@@ -81,76 +81,129 @@
category = list("initial", "Toys")
/datum/design/autoylathe/mech/model1
- name = "Toy Ripley"
+ name = "Toy Ripley MK-I"
id = "toymech1"
materials = list(/datum/material/plastic = 250)
- build_path = /obj/item/toy/prize/ripley
+ build_path = /obj/item/toy/mecha/ripley
+ category = list("hacked", "Figurines")
/datum/design/autoylathe/mech/model2
- name = "Toy Firefighter Ripley"
+ name = "Toy Ripley MK-II"
id = "toymech2"
materials = list(/datum/material/plastic = 250)
- build_path = /obj/item/toy/prize/fireripley
+ build_path = /obj/item/toy/mecha/ripleymkii
+ category = list("hacked", "Figurines")
-/datum/design/autoylathe/mech/contraband/model3
- name = "Toy Deathsquad fireripley "
+/datum/design/autoylathe/mech/model3
+ name = "Toy Hauler"
id = "toymech3"
materials = list(/datum/material/plastic = 250)
- build_path = /obj/item/toy/prize/deathripley
+ build_path = /obj/item/toy/mecha/hauler
+ category = list("hacked", "Figurines")
/datum/design/autoylathe/mech/model4
- name = "Toy Gygax"
+ name = "Toy Clarke"
id = "toymech4"
materials = list(/datum/material/plastic = 250)
- build_path = /obj/item/toy/prize/gygax
+ build_path = /obj/item/toy/mecha/clarke
+ category = list("hacked", "Figurines")
/datum/design/autoylathe/mech/model5
- name = "Toy Durand"
+ name = "Toy Odysseus"
id = "toymech5"
materials = list(/datum/material/plastic = 250)
- build_path = /obj/item/toy/prize/durand
+ build_path = /obj/item/toy/mecha/odysseus
+ category = list("hacked", "Figurines")
-/datum/design/autoylathe/mech/contraband/model6
- name = "Toy H.O.N.K."
+/datum/design/autoylathe/mech/model6
+ name = "Toy Gygax"
id = "toymech6"
materials = list(/datum/material/plastic = 250)
- build_path = /obj/item/toy/prize/honk
+ build_path = /obj/item/toy/mecha/gygax
+ category = list("hacked", "Figurines")
-/datum/design/autoylathe/mech/contraband/model7
- name = "Toy Marauder"
+/datum/design/autoylathe/mech/model7
+ name = "Toy Durand"
id = "toymech7"
materials = list(/datum/material/plastic = 250)
- build_path = /obj/item/toy/prize/marauder
+ build_path = /obj/item/toy/mecha/durand
+ category = list("hacked", "Figurines")
-/datum/design/autoylathe/mech/contraband/model8
- name = "Toy Seraph"
+/datum/design/autoylathe/mech/model8
+ name = "Toy Savannah-Ivanov"
id = "toymech8"
materials = list(/datum/material/plastic = 250)
- build_path = /obj/item/toy/prize/seraph
+ build_path = /obj/item/toy/mecha/savannahivanov
+ category = list("hacked", "Figurines")
-/datum/design/autoylathe/mech/contraband/model9
- name = "Toy Mauler"
+/datum/design/autoylathe/mech/model9
+ name = "Toy Phazon"
id = "toymech9"
materials = list(/datum/material/plastic = 250)
- build_path = /obj/item/toy/prize/mauler
+ build_path = /obj/item/toy/mecha/phazon
+ category = list("hacked", "Figurines")
-/datum/design/autoylathe/mech/model10
- name = "Toy Odysseus"
+/datum/design/autoylathe/mech/contraband/model10
+ name = "Toy H.O.N.K"
id = "toymech10"
materials = list(/datum/material/plastic = 250)
- build_path = /obj/item/toy/prize/odysseus
+ build_path = /obj/item/toy/mecha/honk
+ category = list("hacked", "Figurines")
-/datum/design/autoylathe/mech/model11
- name = "Toy Phazon"
+/datum/design/autoylathe/mech/contraband/model11
+ name = "Toy Dark Gygax"
id = "toymech11"
materials = list(/datum/material/plastic = 250)
- build_path = /obj/item/toy/prize/phazon
+ build_path = /obj/item/toy/mecha/darkgygax
+ category = list("hacked", "Figurines")
/datum/design/autoylathe/mech/contraband/model12
- name = "Toy Reticence"
+ name = "Toy Mauler"
id = "toymech12"
materials = list(/datum/material/plastic = 250)
- build_path = /obj/item/toy/prize/reticence
+ build_path = /obj/item/toy/mecha/mauler
+ category = list("hacked", "Figurines")
+
+/datum/design/autoylathe/mech/contraband/model13
+ name = "Toy Dark H.O.N.K"
+ id = "toymech13"
+ materials = list(/datum/material/plastic = 250)
+ build_path = /obj/item/toy/mecha/darkhonk
+ category = list("hacked", "Figurines")
+
+/datum/design/autoylathe/mech/contraband/model14
+ name = "Toy Death-Ripley"
+ id = "toymech14"
+ materials = list(/datum/material/plastic = 250)
+ build_path = /obj/item/toy/mecha/deathripley
+ category = list("hacked", "Figurines")
+
+/datum/design/autoylathe/mech/contraband/model15
+ name = "Toy Reticence"
+ id = "toymech15"
+ materials = list(/datum/material/plastic = 250)
+ build_path = /obj/item/toy/mecha/reticence
+ category = list("hacked", "Figurines")
+
+/datum/design/autoylathe/mech/contraband/model16
+ name = "Toy Marauder"
+ id = "toymech16"
+ materials = list(/datum/material/plastic = 250)
+ build_path = /obj/item/toy/mecha/marauder
+ category = list("hacked", "Figurines")
+
+/datum/design/autoylathe/mech/contraband/model17
+ name = "Toy Seraph"
+ id = "toymech17"
+ materials = list(/datum/material/plastic = 250)
+ build_path = /obj/item/toy/mecha/seraph
+ category = list("hacked", "Figurines")
+
+/datum/design/autoylathe/mech/model18
+ name = "Toy Firefighter"
+ id = "toymech18"
+ materials = list(/datum/material/plastic = 250)
+ build_path = /obj/item/toy/mecha/firefighter
category = list("hacked", "Figurines")
/datum/design/autoylathe/talking/AI
diff --git a/code/modules/spells/spell_types/wizard.dm b/code/modules/spells/spell_types/wizard.dm
index 0be8cb9196..774f1820ea 100644
--- a/code/modules/spells/spell_types/wizard.dm
+++ b/code/modules/spells/spell_types/wizard.dm
@@ -333,7 +333,7 @@
/obj/item/spellpacket/lightningbolt
name = "\improper Lightning bolt Spell Packet"
desc = "Some birdseed wrapped in cloth that somehow crackles with electricity."
- icon = 'icons/obj/toy.dmi'
+ icon = 'icons/obj/toys/toy.dmi'
icon_state = "snappop"
w_class = WEIGHT_CLASS_TINY
diff --git a/icons/obj/toy.dmi b/icons/obj/toy.dmi
deleted file mode 100644
index e8106905b7..0000000000
Binary files a/icons/obj/toy.dmi and /dev/null differ
diff --git a/icons/obj/toys/toy.dmi b/icons/obj/toys/toy.dmi
new file mode 100644
index 0000000000..8a4826d411
Binary files /dev/null and b/icons/obj/toys/toy.dmi differ
diff --git a/tgstation.dme b/tgstation.dme
index 640bc99969..4a116ff9fd 100644
--- a/tgstation.dme
+++ b/tgstation.dme
@@ -724,6 +724,7 @@
#include "code\datums\elements\photosynthesis.dm"
#include "code\datums\elements\polychromic.dm"
#include "code\datums\elements\scavenging.dm"
+#include "code\datums\elements\series.dm"
#include "code\datums\elements\snail_crawl.dm"
#include "code\datums\elements\spellcasting.dm"
#include "code\datums\elements\squish.dm"
@@ -1207,6 +1208,7 @@
#include "code\game\objects\items\teleprod.dm"
#include "code\game\objects\items\telescopic_iv.dm"
#include "code\game\objects\items\theft_tools.dm"
+#include "code\game\objects\items\toy_mechs.dm"
#include "code\game\objects\items\toys.dm"
#include "code\game\objects\items\trash.dm"
#include "code\game\objects\items\vending_items.dm"