Files
Bubberstation/code/game/objects/structures/training_machine.dm
SkyratBot 717c31d40c [MIRROR] Adds the New-and-Improved Training Machine, Training Toolbox! (#1345)
* Adds the New-and-Improved Training Machine, Training Toolbox! (#54133)

Co-authored-by: Jared-Fogle <35135081+Jared-Fogle@ users.noreply.github.com>
Co-authored-by: ATH1909 <42606352+ATH1909@ users.noreply.github.com>
Co-authored-by: Rohesie <rohesie@ gmail.com>

* Adds the New-and-Improved Training Machine, Training Toolbox!

Co-authored-by: Emmanuel S <mrdoomboyo@gmail.com>
Co-authored-by: Jared-Fogle <35135081+Jared-Fogle@ users.noreply.github.com>
Co-authored-by: ATH1909 <42606352+ATH1909@ users.noreply.github.com>
Co-authored-by: Rohesie <rohesie@ gmail.com>
2020-10-17 00:39:34 +01:00

410 lines
14 KiB
Plaintext

#define MIN_RANGE 1
#define MIN_SPEED 1
#define MAX_RANGE 7
#define MAX_SPEED 10
#define HITS_TO_KILL 9
#define MIN_ATTACK_DELAY 10
#define MAX_ATTACK_DELAY 15
/**
* Machine that runs around wildly so people can practice clickin on things
*
* Can have a mob buckled on or a obj/item/target attached. Movement controlled by SSFastProcess,
* movespeed controlled by cooldown macros. Can attach obj/item/target, obj/item/training_toolbox, and can buckle mobs to this.
*/
/obj/structure/training_machine
name = "AURUMILL-Brand MkII. Personnel Training Machine"
desc = "Used for combat training simulations. Accepts standard training targets. A pair of buckling straps are attached."
icon = 'icons/obj/objects.dmi'
icon_state = "training_machine"
can_buckle = TRUE
buckle_lying = 0
max_integrity = 200
///Is the machine moving? Setting this to FALSE will automatically call stop_moving()
var/moving = FALSE
///The distance the machine is allowed to roam from its starting point
var/range = 1
///A random spot within range that the trainer is trying to reach
var/turf/target_position
///The turf the machine was on when it was activated
var/turf/starting_turf
///Delay between process() calls. Cannot be higher than MAX_SPEED. Smaller value represents faster movement.
var/move_speed = 1
///Reference to a potentially attached object, either a target, trainer toolbox, or syndicate toolbox
var/obj/attached_item
///Helper for timing attacks when emagged
COOLDOWN_DECLARE(attack_cooldown)
///Cooldown macro to control how fast this will move. Used in process()
COOLDOWN_DECLARE(move_cooldown)
/**
* Called on qdel(), so we don't want a cool explosion to happen
*/
/obj/structure/training_machine/Destroy()
remove_attached_item()
return ..()
/**
* Called on a normal destruction, so we have a cool explosion and toss whatever's attached
*/
/obj/structure/training_machine/obj_destruction(damage_flag)
remove_attached_item(throwing = TRUE)
explosion(src, 0,0,1, flame_range = 2)
return ..()
/obj/structure/training_machine/ui_state(mob/user)
return GLOB.physical_state
/obj/structure/training_machine/ui_interact(mob/user, datum/tgui/ui)
ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
ui = new(user, src, "TrainingMachine", name)
ui.open()
/**
* Send data to the UI
*
* Include's the machine's movement range, speed, and whether or not it's active
*/
/obj/structure/training_machine/ui_data(mob/user)
var/list/data = list()
data["range"] = range
data["movespeed"] = move_speed
data["moving"] = moving
return data
/**
* Control the attached variables.
*
* Will not respond if moving and emagged, so once you set it to go it can't be stopped!
*/
/obj/structure/training_machine/ui_act(action, params)
. = ..()
if(.)
return
if (moving && obj_flags & EMAGGED)
visible_message("<span class='warning'>The [src]'s control panel fizzles slightly.</span>")
return
switch(action)
if("toggle")
toggle()
. = TRUE
if("range")
var/range_input = params["range"]
range = clamp(range_input, MIN_RANGE, MAX_RANGE)
. = TRUE
if("movespeed")
var/range_input = params["movespeed"]
move_speed = clamp(range_input, MIN_SPEED, MAX_SPEED)
. = TRUE
/obj/structure/training_machine/attack_hand(mob/user)
ui_interact(user)
/**
* Called when the machien is attacked by something
*
* Meant for attaching an item to the machine, should only be a training toolbox or target. If emagged, the
* machine will gain an auto-attached syndicate toolbox, so in that case we shouldn't be able to swap it out
*/
/obj/structure/training_machine/attackby(obj/item/target, mob/user)
if (user.a_intent != INTENT_HELP)
return ..()
if (!istype(target, /obj/item/training_toolbox) && !istype(target, /obj/item/target))
return ..()
if (obj_flags & EMAGGED)
to_chat(user, "<span class='warning'>The toolbox is somehow stuck on! It won't budge!</span>")
return
attach_item(target)
to_chat(user, "<span class='notice'>You attach \the [attached_item] to the training device.</span>")
playsound(src, "rustle", 50, TRUE)
/**
* Attach an item to the machine
*
* This proc technically works with any obj. Currently is only used for objects of type item/target and item/training_toolbox
* Will make the attached item appear visually on the machine
* Arguments
* * target - The object to attach
*/
/obj/structure/training_machine/proc/attach_item(obj/target)
remove_attached_item()
attached_item = target
attached_item.forceMove(src)
attached_item.vis_flags |= VIS_INHERIT_ID
vis_contents += attached_item
RegisterSignal(attached_item, COMSIG_PARENT_QDELETING, .proc/on_attached_delete)
handle_density()
/**
* Called when the attached item is deleted.
*
* Cleans up behavior for when the attached item is deleted or removed.
*/
/obj/structure/training_machine/proc/on_attached_delete()
UnregisterSignal(attached_item, COMSIG_PARENT_QDELETING)
vis_contents -= attached_item
attached_item = null
handle_density()
/**
* Remove the attached item from the machine
*
* Called when a user removes the item by hand or by swapping it out with another, when the machine breaks, or
* when the machine is emagged.
* Arguments
* * user - The peson , if any, removing the attached item
* * throwing - If we should make the item fly off the machine
*/
/obj/structure/training_machine/proc/remove_attached_item(mob/user, throwing = FALSE)
if (!attached_item)
return
if (istype(attached_item, /obj/item/storage/toolbox/syndicate))
UnregisterSignal(attached_item, COMSIG_PARENT_QDELETING)
qdel(attached_item)
else if (user)
user.put_in_hands(attached_item)
else
attached_item.forceMove(drop_location())
if (throwing && !QDELETED(attached_item)) //Fun little thing where we throw out the old attached item when emagged
//We do a QDELETED check here because we don't want to throw the syndi toolbox, if it exists
var/destination = get_edge_target_turf(get_turf(src), pick(GLOB.alldirs))
attached_item.throw_at(destination, 4, 1)
on_attached_delete()
/obj/structure/training_machine/AltClick(mob/user)
. = ..()
if (!attached_item)
return
if (obj_flags & EMAGGED)
to_chat(user, "<span class='warning'>The toolbox is somehow stuck on! It won't budge!</span>")
return
to_chat(user, "<span class='notice'>You remove \the [attached_item] from the training device.</span>")
remove_attached_item(user)
playsound(src, "rustle", 50, TRUE)
/**
* Toggle the machine's movement
*/
/obj/structure/training_machine/proc/toggle()
if (moving)
stop_moving()
else
start_moving()
/**
* Stop the machine's movement
*
* Will call STOP_PROCESSING, play a sound, and say an appropriate message
* Arguments
* * Message - the message the machine says when stopping
*/
/obj/structure/training_machine/proc/stop_moving(message = "Ending training simulation.")
moving = FALSE
starting_turf = null
say(message)
playsound(src,'sound/machines/synth_no.ogg',50,FALSE)
STOP_PROCESSING(SSfastprocess, src)
/**
* Start the machine's movement
*
* Says a message, plays a sound, then starts processing
*/
/obj/structure/training_machine/proc/start_moving()
moving = TRUE
starting_turf = get_turf(src)
say("Beginning training simulation.")
playsound(src,'sound/machines/triple_beep.ogg',50,FALSE)
START_PROCESSING(SSfastprocess, src)
/**
* Main movement method for the machine
*
* Handles movement using SSFastProcess. Moves randomly, point-to-point, in an area centered around wherever it started.
* Will only move if the move_cooldown cooldown macro is finished.
* If it can't find a place to go, it will stop moving.
*/
/obj/structure/training_machine/process()
if(!COOLDOWN_FINISHED(src, move_cooldown))
return
var/turf/current_turf = get_turf(src)
if (!moving || !starting_turf || isspaceturf(current_turf))
stop_moving()
return
if (current_turf == target_position) //We've reached our target turf, now find a new one
target_position = null
if (!target_position)
target_position = find_target_position()
if (!target_position)
stop_moving("ERROR! Cannot calculate suitable movement path.")
var/turf/nextStep = get_step_towards(src, target_position)
if (!Move(nextStep, get_dir(src, nextStep)))
target_position = null //We couldn't move towards the target turf, so find a new target turf
try_attack()
COOLDOWN_START(src, move_cooldown, max(MAX_SPEED - move_speed, 1))
/**
* Find a suitable turf to move towards
*/
/obj/structure/training_machine/proc/find_target_position()
var/list/turfs = list()
for(var/turf/potential_turf in view(range, starting_turf))
if (potential_turf.is_blocked_turf() || potential_turf == target_position)
continue
turfs += potential_turf
if (!length(turfs))
return
return pick(turfs)
/**
* Try to attack a nearby mob
*
* Called whenever the machine moves, this will look for mobs adjacent to the machine to attack.
* Will attack with either a training toolbox (if attached), or a much more deadly syndicate toolbox (if emagged).
* A cooldown macro (attack_cooldown) ensures it doesn't attack too quickly
*/
/obj/structure/training_machine/proc/try_attack()
if (!attached_item || istype(attached_item, /obj/item/target))
return
if (!COOLDOWN_FINISHED(src, attack_cooldown))
return
var/list/targets
for(var/mob/living/carbon/target in oview(1, get_turf(src))) //Find adjacent target
if (target.stat == CONSCIOUS && target.Adjacent(src))
LAZYADD(targets, target)
var/mob/living/carbon/target = pick(targets)
if (!target)
return
do_attack_animation(target, null, attached_item)
if (obj_flags & EMAGGED)
target.apply_damage(attached_item.force, BRUTE, BODY_ZONE_CHEST)
playsound(src, 'sound/weapons/smash.ogg', 15, TRUE)
COOLDOWN_START(src, attack_cooldown, rand(MIN_ATTACK_DELAY, MAX_ATTACK_DELAY))
/**
* Make sure the machine can't be walked through if something is attached
*/
/obj/structure/training_machine/proc/handle_density()
if(length(buckled_mobs) || attached_item)
density = TRUE
else
density = FALSE
/obj/structure/training_machine/buckle_mob(mob/living/M, force = FALSE, check_loc = TRUE)
. = ..()
if (istype(attached_item, /obj/item/target))
return FALSE
/obj/structure/training_machine/post_buckle_mob()
handle_density()
return ..()
/obj/structure/training_machine/post_unbuckle_mob()
handle_density()
return ..()
/**
* Emagging causes a deadly, unremovable syndicate toolbox to be attached to the machine
*/
/obj/structure/training_machine/emag_act(mob/user)
. = ..()
if (obj_flags & EMAGGED)
return
obj_flags |= EMAGGED
remove_attached_item(throwing = TRUE) //Toss out the old attached item!
attach_item(new /obj/item/storage/toolbox/syndicate(src))
to_chat(user, "<span class='warning'>You override the training machine's safety protocols, and activate its realistic combat feature. A toolbox pops out of a slot on the top.</span>")
playsound(src, 'sound/machines/click.ogg', 50, TRUE)
add_overlay("evil_trainer")
/obj/structure/training_machine/examine(mob/user)
. = ..()
if (obj_flags & EMAGGED)
. += "<span class='warning'>It has a dangerous-looking toolbox attached to it, and the control panel is smoking sightly...</span>"
else if (attached_item) //Can't removed the syndicate toolbox!
. += "<span class='notice'><b>Alt-Click to remove \the [attached_item]</b></span>"
. += "<span class='notice'><b>Click to open control interface.</b></span>"
/**
* Device that simply counts the number of times you've hit a mob or target with. Looks like a toolbox but isn't.
*
* Also has a 'Lap' function for keeping track of hits made at a certain point. Also, looks kinda like his grace for laughs and pranks.
*/
/obj/item/training_toolbox
name = "Training Toolbox"
desc = "AURUMILL-Brand Baby's First Training Toolbox. A digital display on the back keeps track of hits made by the user. Second toolbox sold seperately!"
icon_state = "his_grace_ascended"
inhand_icon_state = "toolbox_gold"
lefthand_file = 'icons/mob/inhands/equipment/toolbox_lefthand.dmi'
righthand_file = 'icons/mob/inhands/equipment/toolbox_righthand.dmi'
flags_1 = CONDUCT_1
force = 0
throwforce = 0
throw_speed = 2
throw_range = 7
w_class = WEIGHT_CLASS_BULKY
///Total number of hits made against a valid target
var/total_hits = 0
///Number of hits made since the Lap button (alt-click) was last pushed
var/lap_hits = 0
/obj/item/training_toolbox/afterattack(atom/target, mob/user, proximity)
. = ..()
if (!proximity || target == user || user.a_intent == INTENT_HELP)
return
if (check_hit(target))
user.changeNext_move(CLICK_CD_MELEE)
/**
* Check if we should increment the hit counter
*
* Increments the 'hit' counter if the target we're attacking is a mob, target, or training machine with an attached item.
* Will beep every 9 hits, as 9 hits usually signifies a KO with a normal toolbox
* Arguments
* * target - the atom we're hitting
*/
/obj/item/training_toolbox/proc/check_hit(atom/target)
var/target_is_machine = istype(target, /obj/structure/training_machine)
if (!ismob(target) && !istype(target, /obj/item/target) && !target_is_machine)
return FALSE
if (target_is_machine)
var/obj/structure/training_machine/trainer = target
if (!trainer.attached_item)
return FALSE
total_hits++
lap_hits++
playsound(src,'sound/weapons/smash.ogg',50,FALSE)
if (lap_hits % HITS_TO_KILL == 0)
playsound(src,'sound/machines/twobeep.ogg',25,FALSE)
return TRUE
/obj/item/training_toolbox/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum)
. = ..()
if (!.)
check_hit(hit_atom)
/obj/item/training_toolbox/AltClick(mob/user)
. = ..()
to_chat(user, "<span class='notice'>You push the 'Lap' button on the toolbox's display.</span>")
lap_hits = initial(lap_hits)
/obj/item/training_toolbox/examine(mob/user)
. = ..()
if(!in_range(src, user) && !isobserver(user))
. += "<span class='notice'>You can see a display on the back. You'll need to get closer to read it, though.</span>"
return
. += "<span class='notice'>A display on the back reads:</span>"
. += "<span class='notice'>Total Hits: <b>[total_hits]</b></span>"
if (lap_hits != total_hits)
. += "<span class='notice'>Current Lap: <b>[lap_hits]</b></span>"
. += "<span class='notice'><b>Alt-Click to 'Lap' the hit counter.</b></span>"
#undef MIN_RANGE
#undef MIN_SPEED
#undef MAX_RANGE
#undef MAX_SPEED
#undef HITS_TO_KILL
#undef MIN_ATTACK_DELAY
#undef MAX_ATTACK_DELAY