mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2026-08-24 21:48:39 +01:00
Merge branch 'master' of https://github.com/tgstation/tgstation into upstream-23-10-2025
This commit is contained in:
@@ -0,0 +1,673 @@
|
||||
#define ENGINE_UNWRENCHED 0
|
||||
#define ENGINE_WRENCHED 1
|
||||
#define ENGINE_WELDED 2
|
||||
#define CARGO_HITBOX_LAYER (ABOVE_ALL_MOB_LAYER)
|
||||
#define CART_ROOF_LAYER (CARGO_HITBOX_LAYER + 0.01)
|
||||
#define CART_LOWER_LAYER (OBJ_LAYER)
|
||||
#define FAKE_GLIDE_INITIAL_SOURCE "fake_glide_initial"
|
||||
#define GOLFCART_RUN_OVER_DAMAGE (25)
|
||||
|
||||
/obj/vehicle/ridden/golfcart
|
||||
name = "golf cart"
|
||||
desc = "An all-purpose cargo hauling vehicle."
|
||||
icon = 'icons/obj/toys/golfcart_split.dmi'
|
||||
icon_state = "front"
|
||||
max_integrity = 100
|
||||
armor_type = /datum/armor/none
|
||||
pass_flags_self = parent_type::pass_flags_self | LETPASSCLICKS
|
||||
integrity_failure = 0.5
|
||||
layer = ABOVE_MOB_LAYER
|
||||
max_occupants = 1
|
||||
key_type = /obj/item/key/golfcart
|
||||
///Perform an extra step after movement finishes?
|
||||
var/perform_extra_step = FALSE
|
||||
///Base movespeed before any modifiers. Humans run at 1.5 movedelay.
|
||||
var/static/base_movedelay = 1.25
|
||||
///Base movespeed for the hotrod before any modifiers
|
||||
var/static/hotrod_base_movedelay = 0.65
|
||||
///Particle holder for low integrity smoking
|
||||
var/obj/effect/abstract/particle_holder/smoke = null
|
||||
///Seperate image for the cargo buckled to the rear
|
||||
var/image/cargo_image = null
|
||||
///The power source for the cart. Can be replaced with an engine.
|
||||
var/obj/item/stock_parts/power_store/cell/cell = null
|
||||
///A more powerful power source for the cart.
|
||||
var/obj/item/v8_engine/engine = null
|
||||
///Can be unwrenched, wrenched, or welded
|
||||
var/engine_state = null
|
||||
///An invisible sprite that exists as a hitbox
|
||||
var/obj/golfcart_rear/child = null
|
||||
///Objects that can be buckled to the cargo hitch
|
||||
var/static/list/allowed_cargo = typecacheof(list(
|
||||
/obj/structure/closet/crate,
|
||||
/obj/structure/reagent_dispensers,
|
||||
/obj/structure/flatpack_cart,
|
||||
/obj/machinery,
|
||||
/obj/item/kirbyplants,
|
||||
))
|
||||
///Each movement requires this much energy to be drawn from the internal cell
|
||||
var/charge_per_move = STANDARD_CELL_CHARGE / 300
|
||||
///Has the final say on whether something can be buckled.
|
||||
var/static/list/banned_cargo = typecacheof(list(
|
||||
/obj/structure/reagent_dispensers/wall,
|
||||
// i mean it's a fucking door
|
||||
/obj/machinery/door,
|
||||
))
|
||||
///Is the hood open?
|
||||
var/hood_open = FALSE
|
||||
|
||||
/obj/item/key/golfcart
|
||||
name = "golfcart key"
|
||||
desc = "A small grey key for using the golf cart."
|
||||
icon = 'icons/obj/toys/golfcart_split.dmi'
|
||||
|
||||
/obj/item/golfcart_kit
|
||||
name = "golfcart parts kit"
|
||||
desc = "A box containing a golf cart. Some assembly required. Batteries not included."
|
||||
icon = 'icons/obj/toys/golfcart_split.dmi'
|
||||
icon_state = "parts_kit"
|
||||
w_class = WEIGHT_CLASS_HUGE
|
||||
throw_range = 2
|
||||
item_flags = SLOWS_WHILE_IN_HAND | IMMUTABLE_SLOW
|
||||
slowdown = 2.5
|
||||
drag_slowdown = 3.5
|
||||
|
||||
/obj/item/golfcart_kit/examine(mob/user)
|
||||
. = ..()
|
||||
. += span_notice("The instructions say that it needs to be [EXAMINE_HINT("screwed")] together.")
|
||||
|
||||
/obj/item/golfcart_kit/proc/play_building_noises(mob/living/user, duration)
|
||||
duration = max(duration - (1 SECONDS), 0.5 SECONDS)
|
||||
playsound(src, 'sound/items/poster/poster_ripped.ogg', 50, TRUE)
|
||||
sleep(1 SECONDS)
|
||||
if (!DOING_INTERACTION_WITH_TARGET(user, src))
|
||||
return
|
||||
playsound(src, 'sound/items/tools/screwdriver_operating.ogg', 50, TRUE)
|
||||
sleep(duration / 2)
|
||||
if (!DOING_INTERACTION_WITH_TARGET(user, src))
|
||||
return
|
||||
playsound(src, 'sound/items/tools/ratchet.ogg', 50, TRUE)
|
||||
sleep(duration / 2)
|
||||
if (!DOING_INTERACTION_WITH_TARGET(user, src))
|
||||
return
|
||||
playsound(src, 'sound/items/tools/screwdriver.ogg', 50, TRUE)
|
||||
|
||||
/obj/item/golfcart_kit/screwdriver_act(mob/living/user, obj/item/tool)
|
||||
if (!isturf(loc))
|
||||
user.balloon_alert(user, "set down first!")
|
||||
return ITEM_INTERACT_BLOCKING
|
||||
user.visible_message(span_notice("[user] starts putting together the [src]..."), span_notice("You start assembling the [src]..."))
|
||||
var/unboxing_duration = 7 SECONDS
|
||||
INVOKE_ASYNC(src, PROC_REF(play_building_noises), user, unboxing_duration * tool.toolspeed)
|
||||
if(!tool.use_tool(src, user, unboxing_duration))
|
||||
return ITEM_INTERACT_BLOCKING
|
||||
if (!isturf(loc))
|
||||
return ITEM_INTERACT_BLOCKING
|
||||
var/obj/vehicle/ridden/golfcart/cart = new(get_turf(src))
|
||||
user.visible_message(span_notice("[user] assembles the [cart]!"), span_notice("You assemble the [cart]."))
|
||||
qdel(src)
|
||||
|
||||
/obj/vehicle/ridden/golfcart/atom_break()
|
||||
. = ..()
|
||||
if (smoke)
|
||||
return
|
||||
smoke = new(src, /particles/smoke/ash)
|
||||
|
||||
/obj/vehicle/ridden/golfcart/atom_fix()
|
||||
. = ..()
|
||||
if (smoke)
|
||||
QDEL_NULL(smoke)
|
||||
|
||||
/obj/vehicle/ridden/golfcart/atom_destruction()
|
||||
explosion(src, devastation_range = -1, light_impact_range = 2, flame_range = 3, flash_range = 4)
|
||||
return ..()
|
||||
|
||||
///Jiggles the cargo_image up and down.
|
||||
/obj/vehicle/ridden/golfcart/proc/shake_cargo(pixelshiftx = 2, pixelshifty = 2, duration)
|
||||
if (!cargo_image)
|
||||
return
|
||||
var/inital_pixel_x = cargo_image.pixel_x
|
||||
var/inital_pixel_y = cargo_image.pixel_y
|
||||
animate(cargo_image, pixel_x = inital_pixel_x + rand(-pixelshiftx, pixelshiftx), pixel_y = inital_pixel_y + rand(pixelshifty/2, pixelshifty), time=duration, flags=ANIMATION_PARALLEL)
|
||||
animate(pixel_x = inital_pixel_x, pixel_y = inital_pixel_y, time=duration)
|
||||
|
||||
/obj/vehicle/ridden/golfcart/proc/is_hotrod()
|
||||
return engine && engine_state == ENGINE_WELDED
|
||||
|
||||
///Called when something we crash into lands after being flinged
|
||||
/obj/vehicle/ridden/golfcart/proc/thrown_mob_landed(atom/thrown_atom)
|
||||
if (!isliving(thrown_atom))
|
||||
UnregisterSignal(thrown_atom, COMSIG_MOVABLE_THROW_LANDED)
|
||||
return
|
||||
var/mob/living/thrown_mob = thrown_atom
|
||||
thrown_mob.Knockdown(3 SECONDS)
|
||||
UnregisterSignal(thrown_atom, COMSIG_MOVABLE_THROW_LANDED)
|
||||
|
||||
/obj/vehicle/ridden/golfcart/Bump(atom/bumped_atom)
|
||||
if (..())
|
||||
return
|
||||
if (!is_hotrod())
|
||||
return
|
||||
if(!isliving(bumped_atom))
|
||||
return
|
||||
var/mob/living/mob = bumped_atom
|
||||
mob.throw_at(get_edge_target_turf(mob, dir), 2, 3)
|
||||
RegisterSignal(mob, COMSIG_MOVABLE_THROW_LANDED, PROC_REF(thrown_mob_landed))
|
||||
mob.visible_message(
|
||||
span_danger("[src] hits [mob] at full speed!"),
|
||||
span_userdanger("[src] slams into you!"),
|
||||
)
|
||||
|
||||
///Called when a resting victim is run over
|
||||
/obj/vehicle/ridden/golfcart/proc/run_over(mob/living/victim)
|
||||
if (!has_gravity())
|
||||
victim.throw_at(get_edge_target_turf(victim, dir), 4, 3)
|
||||
RegisterSignal(victim, COMSIG_MOVABLE_THROW_LANDED, PROC_REF(thrown_mob_landed))
|
||||
return
|
||||
if (istype(victim, /mob/living/carbon))
|
||||
var/mob/living/carbon/person = victim
|
||||
if (person.body_position == LYING_DOWN)
|
||||
log_combat(src, victim, "run over", addition = "(DAMTYPE: [uppertext(BRUTE)])")
|
||||
playsound(src, 'sound/effects/pop_expl.ogg', 50, TRUE)
|
||||
playsound(src, 'sound/effects/splat.ogg', 50, TRUE)
|
||||
victim.visible_message(
|
||||
span_danger("[src] drives over [victim]!"),
|
||||
span_userdanger("[src] drives over you!"),
|
||||
)
|
||||
|
||||
var/damage = rand(GOLFCART_RUN_OVER_DAMAGE - GOLFCART_RUN_OVER_DAMAGE / 5, GOLFCART_RUN_OVER_DAMAGE + GOLFCART_RUN_OVER_DAMAGE / 5)
|
||||
//adds up to exactly 1
|
||||
person.apply_damage((1/3) * damage, BRUTE, BODY_ZONE_HEAD)
|
||||
person.apply_damage((1/3) * damage, BRUTE, BODY_ZONE_CHEST)
|
||||
person.apply_damage((1/12) * damage, BRUTE, BODY_ZONE_L_LEG)
|
||||
person.apply_damage((1/12) * damage, BRUTE, BODY_ZONE_R_LEG)
|
||||
person.apply_damage((1/12) * damage, BRUTE, BODY_ZONE_L_ARM)
|
||||
person.apply_damage((1/12) * damage, BRUTE, BODY_ZONE_R_ARM)
|
||||
|
||||
add_mob_blood(person)
|
||||
var/turf/below_us = get_turf(src)
|
||||
below_us.add_mob_blood(person)
|
||||
|
||||
AddComponent(/datum/component/blood_walk, \
|
||||
blood_type = /obj/effect/decal/cleanable/blood/tracks, \
|
||||
target_dir_change = TRUE, \
|
||||
transfer_blood_dna = TRUE, \
|
||||
max_blood = 4)
|
||||
|
||||
/obj/vehicle/ridden/golfcart/Moved(atom/old_loc, movement_dir, forced, list/old_locs, momentum_change = TRUE)
|
||||
. = ..()
|
||||
if (!is_hotrod())
|
||||
return
|
||||
for(var/mob/living/future_pancake in loc)
|
||||
run_over(future_pancake)
|
||||
|
||||
/obj/vehicle/ridden/golfcart/mouse_drop_receive(atom/dropped, mob/user, params)
|
||||
if (child.can_load(dropped))
|
||||
return child.mouse_drop_receive(dropped, user, params)
|
||||
return ..()
|
||||
|
||||
/obj/vehicle/ridden/golfcart/item_interaction(mob/living/user, obj/item/attacking_item, list/modifiers)
|
||||
if (!hood_open)
|
||||
return ..()
|
||||
if (istype(attacking_item, /obj/item/v8_engine))
|
||||
if (engine || cell)
|
||||
balloon_alert(user, "already has an engine!")
|
||||
return ITEM_INTERACT_BLOCKING
|
||||
user.transferItemToLoc(attacking_item, src)
|
||||
engine = attacking_item
|
||||
engine_state = ENGINE_UNWRENCHED
|
||||
balloon_alert(user, "installed \the [engine]")
|
||||
return ITEM_INTERACT_SUCCESS
|
||||
if (istype(attacking_item, /obj/item/stock_parts/power_store/cell))
|
||||
if (cell || engine)
|
||||
balloon_alert(user, "already has an engine!")
|
||||
return ITEM_INTERACT_BLOCKING
|
||||
user.transferItemToLoc(attacking_item, src)
|
||||
cell = attacking_item
|
||||
balloon_alert(user, "installed \the [cell]")
|
||||
return ITEM_INTERACT_SUCCESS
|
||||
return ..()
|
||||
|
||||
/obj/vehicle/ridden/golfcart/attack_hand(mob/living/user, list/modifiers)
|
||||
if (!hood_open)
|
||||
return ..()
|
||||
if (engine && engine_state == ENGINE_UNWRENCHED)
|
||||
. = TRUE
|
||||
var/obj/item/engine_item = engine
|
||||
engine_state = null
|
||||
engine = null
|
||||
if (user.put_in_hands(engine_item))
|
||||
return
|
||||
engine_item.forceMove(drop_location())
|
||||
return
|
||||
if (isnull(cell))
|
||||
return ..()
|
||||
var/obj/item/stock_parts/power_store/cell/cell_to_take = cell
|
||||
cell = null
|
||||
. = TRUE
|
||||
if (user.put_in_hands(cell_to_take))
|
||||
return
|
||||
cell_to_take.forceMove(drop_location())
|
||||
|
||||
/obj/vehicle/ridden/golfcart/proc/can_wrench_engine()
|
||||
return hood_open && engine && (engine_state == ENGINE_UNWRENCHED || engine_state == ENGINE_WRENCHED)
|
||||
|
||||
/obj/vehicle/ridden/golfcart/proc/can_weld_engine()
|
||||
return hood_open && engine && (engine_state == ENGINE_WRENCHED || engine_state == ENGINE_WELDED)
|
||||
|
||||
/obj/vehicle/ridden/golfcart/proc/set_engine_state(state)
|
||||
engine_state = state
|
||||
if (engine_state == ENGINE_WELDED && icon != 'icons/obj/toys/golfcart_hotrod_split.dmi')
|
||||
icon = 'icons/obj/toys/golfcart_hotrod_split.dmi'
|
||||
update_appearance(UPDATE_ICON)
|
||||
else if (engine_state != ENGINE_WELDED && icon != initial(icon))
|
||||
icon = initial(icon)
|
||||
|
||||
/obj/vehicle/ridden/golfcart/wrench_act(mob/living/user, obj/item/tool)
|
||||
if (!can_wrench_engine())
|
||||
return ..()
|
||||
tool.play_tool_sound(src, 50)
|
||||
if (!tool.use_tool(src, user, 3 SECONDS, extra_checks = CALLBACK(src, PROC_REF(can_wrench_engine))))
|
||||
return ITEM_INTERACT_BLOCKING
|
||||
if (engine_state == ENGINE_WRENCHED)
|
||||
set_engine_state(ENGINE_UNWRENCHED)
|
||||
else if (engine_state == ENGINE_UNWRENCHED)
|
||||
set_engine_state(ENGINE_WRENCHED)
|
||||
playsound(src, 'sound/items/deconstruct.ogg', 50, TRUE)
|
||||
return ITEM_INTERACT_SUCCESS
|
||||
|
||||
/obj/vehicle/ridden/golfcart/welder_act(mob/living/user, obj/item/tool)
|
||||
if (user.combat_mode)
|
||||
return
|
||||
if(!tool.tool_start_check(user, heat_required = HIGH_TEMPERATURE_REQUIRED, amount = 1))
|
||||
return ITEM_INTERACT_BLOCKING
|
||||
. = ITEM_INTERACT_SUCCESS
|
||||
if (hood_open)
|
||||
if(!tool.use_tool(src, user, 3 SECONDS, amount = 1, volume = 50, extra_checks = CALLBACK(src, PROC_REF(can_weld_engine))))
|
||||
return ITEM_INTERACT_BLOCKING
|
||||
if (engine_state == ENGINE_WRENCHED)
|
||||
set_engine_state(ENGINE_WELDED)
|
||||
else if (engine_state == ENGINE_WELDED)
|
||||
set_engine_state(ENGINE_WRENCHED)
|
||||
else
|
||||
if(DOING_INTERACTION(user, src))
|
||||
balloon_alert(user, "already repairing it!")
|
||||
return
|
||||
if(atom_integrity >= max_integrity)
|
||||
balloon_alert(user, "it's not damaged!")
|
||||
return
|
||||
// takes 10 seconds to repair from full
|
||||
balloon_alert(user, "started repairing")
|
||||
if (!tool.use_tool(src, user, ((max_integrity - atom_integrity) / max_integrity * 10) SECONDS, volume = 50))
|
||||
balloon_alert(user, "repair interrupted!")
|
||||
return
|
||||
repair_damage(max_integrity - atom_integrity)
|
||||
balloon_alert(user, "repaired")
|
||||
return
|
||||
|
||||
/obj/vehicle/ridden/golfcart/proc/toggle_hood()
|
||||
hood_open = !hood_open
|
||||
update_appearance(UPDATE_ICON)
|
||||
|
||||
/obj/vehicle/ridden/golfcart/click_alt(mob/user)
|
||||
if (user in buckled_mobs)
|
||||
return ..()
|
||||
else
|
||||
to_chat(user, span_warning("You must be sitting down to remove the key!"))
|
||||
. = CLICK_ACTION_SUCCESS
|
||||
toggle_hood()
|
||||
if (hood_open)
|
||||
to_chat(user, span_notice("You pop \the [src]'s hood."))
|
||||
else
|
||||
to_chat(user, span_notice("You shut \the [src]'s hood."))
|
||||
|
||||
/obj/vehicle/ridden/golfcart/examine_more(mob/user)
|
||||
. = ..()
|
||||
if (!child.cargo)
|
||||
return
|
||||
. += span_slightly_larger("It is currently transporting the [child.cargo]")
|
||||
. += child.cargo.examine(user)
|
||||
|
||||
/obj/vehicle/ridden/golfcart/examine(mob/user)
|
||||
. = ..()
|
||||
. += span_notice("Pop the hood by alt-clicking while not riding it.")
|
||||
if (child.cargo)
|
||||
. += span_info("The bed is holding \the [child.cargo].")
|
||||
if(!in_range(user, src) && !issilicon(user) && !isobserver(user))
|
||||
. += span_warning("You're too far away to examine [src] closely.")
|
||||
return
|
||||
if (!engine)
|
||||
var/power = 0
|
||||
if (cell)
|
||||
power = floor(cell.charge / cell.maxcharge * 100)
|
||||
. += span_info("It is currently is at [power]% charge.")
|
||||
if (hood_open)
|
||||
. += span_warning("The hood is open!")
|
||||
if (engine)
|
||||
. += span_info("You can see \the [engine] inside.")
|
||||
if (engine_state == ENGINE_UNWRENCHED)
|
||||
. += span_notice("It needs to be [EXAMINE_HINT("wrenched")] into place.")
|
||||
else if (engine_state == ENGINE_WRENCHED)
|
||||
. += span_notice("It needs to be [EXAMINE_HINT("welded")] down.")
|
||||
// last state is ENGINE_WELDED
|
||||
else if (cell)
|
||||
. += span_info("You can see \the [cell] inside.")
|
||||
. += span_smallnotice("If you remove the cell you could probably install another power source...")
|
||||
else
|
||||
. += span_info("There is no power cell installed.")
|
||||
|
||||
///Called when something tries to pass us. Returns TRUE if it is trying to crawl past us.
|
||||
/obj/vehicle/ridden/golfcart/proc/allow_crawler_through(atom/crawler)
|
||||
if (!isliving(crawler))
|
||||
return FALSE
|
||||
var/mob/living/living_crawler = crawler
|
||||
return living_crawler.body_position == LYING_DOWN
|
||||
|
||||
/obj/vehicle/ridden/golfcart/CanAllowThrough(atom/movable/mover, border_dir)
|
||||
. = ..()
|
||||
if (mover == child)
|
||||
return TRUE
|
||||
if (mover in child.buckled_mobs)
|
||||
return TRUE
|
||||
if (allow_crawler_through(mover))
|
||||
return TRUE
|
||||
|
||||
///Called for COMSIG_MOVABLE_PRE_MOVE on the golfcart front. If the rear of the cart doesn't fit where we want to go, block movement.
|
||||
/obj/vehicle/ridden/golfcart/proc/pre_move(atom/source, atom/new_loc)
|
||||
SIGNAL_HANDLER
|
||||
|
||||
// see if space behind new loc is free
|
||||
var/atom/behind = get_step(new_loc, turn(dir, 180))
|
||||
if ((!behind.Enter(child, child.loc)) && behind != get_step(src, 0))
|
||||
return COMPONENT_MOVABLE_BLOCK_PRE_MOVE
|
||||
// otherwise permit move
|
||||
return
|
||||
|
||||
///Makes movedelay a factor of base_movedelay
|
||||
/obj/vehicle/ridden/golfcart/proc/set_movedelay_effect(modification)
|
||||
var/base_movedelay_effect = base_movedelay
|
||||
if (is_hotrod())
|
||||
base_movedelay_effect = hotrod_base_movedelay
|
||||
movedelay = base_movedelay_effect * modification
|
||||
child.set_glide_size(DELAY_TO_GLIDE_SIZE(movedelay))
|
||||
|
||||
///Sets perform_extra_step to TRUE if we are going up stairs.
|
||||
/obj/vehicle/ridden/golfcart/zMove(dir, turf/target, z_move_flags)
|
||||
var/can_do_extra_step = FALSE
|
||||
if (currently_z_moving == CURRENTLY_Z_ASCENDING)
|
||||
can_do_extra_step = TRUE
|
||||
. = ..()
|
||||
if (!.)
|
||||
return
|
||||
perform_extra_step = perform_extra_step || can_do_extra_step
|
||||
|
||||
///Creates a fake glide effect on the golfcart and anything buckled to it. Called when moving up stairs as moving up stairs creates a two-tile move.
|
||||
/obj/vehicle/ridden/golfcart/proc/fake_glide(direct)
|
||||
var/px = 0
|
||||
var/py = 0
|
||||
if (direct & NORTH)
|
||||
py -= 32
|
||||
else if (direct & SOUTH)
|
||||
py += 32
|
||||
if (direct & EAST)
|
||||
px -= 32
|
||||
else if (direct & WEST)
|
||||
px += 32
|
||||
pixel_x += px
|
||||
pixel_y += py
|
||||
child.pixel_x += px
|
||||
child.pixel_y += py
|
||||
animate(src, movedelay, pixel_x=0, pixel_y=0)
|
||||
animate(child, movedelay, pixel_x=0, pixel_y=0)
|
||||
for (var/mob/buckled in (buckled_mobs + child.buckled_mobs))
|
||||
var/inital_pixel_x = buckled.pixel_x
|
||||
var/inital_pixel_y = buckled.pixel_y
|
||||
buckled.pixel_x += px
|
||||
buckled.pixel_y += py
|
||||
animate(buckled, movedelay, pixel_x=inital_pixel_x, pixel_y=inital_pixel_y)
|
||||
if (buckled.client)
|
||||
var/client/client = buckled.client
|
||||
var/initial_client_pixel_x = client.pixel_x
|
||||
var/initial_client_pixel_y = client.pixel_y
|
||||
client.pixel_x += px
|
||||
client.pixel_y += py
|
||||
animate(client, movedelay, pixel_x=initial_client_pixel_x, pixel_y=initial_client_pixel_y)
|
||||
|
||||
/obj/vehicle/ridden/golfcart/Move(atom/newloc, direct, glide_size_override = 0, update_dir = TRUE)
|
||||
perform_extra_step = FALSE
|
||||
var/atom/old_loc = get_turf(src)
|
||||
var/old_dir = dir
|
||||
if (get_turf(child) == newloc)
|
||||
set_movedelay_effect(2)
|
||||
. = ..(newloc, turn(direct, 180))
|
||||
else
|
||||
set_movedelay_effect(1)
|
||||
. = ..()
|
||||
var/atom/behind = get_step(src, turn(dir, 180))
|
||||
if (old_dir != dir && get_turf(src) == old_loc)
|
||||
if (!behind.Enter(child, child.loc))
|
||||
setDir(old_dir)
|
||||
behind = get_step(src, turn(dir, 180))
|
||||
update_appearance(UPDATE_ICON)
|
||||
child.move_from_parent(behind)
|
||||
// working theory is that somehow the cart can't move onto stairs -> perform_extra_step is true but loc is never set to newloc
|
||||
if ((perform_extra_step && loc.z != old_loc.z) || loc.z < old_loc.z)
|
||||
// this should be called when descending Z levels, or when going up stairs
|
||||
if (!Move(get_step(src, dir), dir, glide_size_override, update_dir))
|
||||
return
|
||||
fake_glide(dir)
|
||||
|
||||
///Called for COMSIG_ATOM_TRIED_PASS on the golfcart front. Allows mobs buckled to the rear of the cart to not get blocked by the front of the cart.
|
||||
/obj/vehicle/ridden/golfcart/proc/allow_movement_between_passengers(atom/source, atom/mover)
|
||||
SIGNAL_HANDLER
|
||||
|
||||
if (mover in child.buckled_mobs)
|
||||
return COMSIG_COMPONENT_PERMIT_PASSAGE
|
||||
|
||||
/obj/vehicle/ridden/golfcart/Initialize(mapload, direction = null)
|
||||
. = ..()
|
||||
AddElement(/datum/element/ridable, /datum/component/riding/vehicle/golfcart)
|
||||
RegisterSignal(src, COMSIG_MOVABLE_PRE_MOVE, PROC_REF(pre_move))
|
||||
child = new /obj/golfcart_rear(null, src)
|
||||
if (isnull(direction))
|
||||
if (get_step(src, NORTH).Enter(child))
|
||||
direction = NORTH
|
||||
else if (get_step(src, EAST).Enter(child))
|
||||
direction = EAST
|
||||
else if (get_step(src, WEST).Enter(child))
|
||||
direction = WEST
|
||||
else if (get_step(src, SOUTH).Enter(child))
|
||||
direction = SOUTH
|
||||
else
|
||||
direction = SOUTH
|
||||
direction = turn(direction, 180)
|
||||
setDir(direction)
|
||||
child.loc = get_step(src, turn(dir, 180))
|
||||
update_appearance()
|
||||
|
||||
/obj/vehicle/ridden/golfcart/loaded/Initialize(mapload)
|
||||
. = ..()
|
||||
cell = new /obj/item/stock_parts/power_store/cell/lead(src)
|
||||
|
||||
/obj/vehicle/ridden/golfcart/hotrod/Initialize(mapload)
|
||||
. = ..()
|
||||
engine = new /obj/item/v8_engine(src)
|
||||
set_engine_state(ENGINE_WELDED)
|
||||
|
||||
/obj/vehicle/ridden/golfcart/update_appearance(updates=ALL)
|
||||
. = ..()
|
||||
child.setDir(dir)
|
||||
child.update_appearance(updates)
|
||||
|
||||
/obj/vehicle/ridden/golfcart/proc/get_cargo_offsets()
|
||||
var/crate_x_offset = 0
|
||||
var/crate_y_offset = 0
|
||||
if (dir & NORTH)
|
||||
crate_y_offset += -30
|
||||
else if (dir & SOUTH)
|
||||
crate_y_offset += 30
|
||||
else if (dir & EAST)
|
||||
crate_x_offset += -32
|
||||
else if (dir & WEST)
|
||||
crate_x_offset += 32
|
||||
return vector(crate_x_offset, crate_y_offset)
|
||||
|
||||
///Flattens the attached cargo into a list of mutable_appearances with proper layering to fit between layer and max_layer
|
||||
/obj/vehicle/ridden/golfcart/proc/generate_cargo_overlay(crate_x_offset = 0, crate_y_offset = 0, layer=null, max_layer=null, shift_all=TRUE)
|
||||
if (!child.cargo)
|
||||
return
|
||||
var/obj/cargo = child.cargo
|
||||
if (!layer)
|
||||
layer = src.layer
|
||||
if (!max_layer)
|
||||
max_layer = ABOVE_ALL_MOB_LAYER
|
||||
var/vector/offsets = get_cargo_offsets()
|
||||
offsets.x += crate_x_offset
|
||||
offsets.y += crate_y_offset
|
||||
var/crate_layer_offset = 0
|
||||
if (dir & NORTH)
|
||||
crate_layer_offset = 0.01
|
||||
else if (dir & SOUTH)
|
||||
crate_layer_offset = -0.01
|
||||
var/list/overlays = list()
|
||||
if (cargo.icon)
|
||||
overlays += mutable_appearance(cargo.icon, cargo.icon_state, cargo.layer)
|
||||
overlays += cargo.update_overlays()
|
||||
var/base_cargo_layer = layer + crate_layer_offset
|
||||
for(var/i in 1 to overlays.len)
|
||||
var/entry = overlays[i]
|
||||
var/mutable_appearance/overlay = entry
|
||||
if(istext(entry))
|
||||
overlay = mutable_appearance(cargo.icon, entry, base_cargo_layer)
|
||||
overlays[i] = overlay
|
||||
// nested lists may exist and i can't be asked to flatten them
|
||||
if (!isnull(overlay))
|
||||
// preserves relative offsets
|
||||
if (overlay.layer > 0)
|
||||
overlay.layer = min(base_cargo_layer + (overlay.layer - cargo.layer), max_layer)
|
||||
else
|
||||
overlay.layer = min(base_cargo_layer + (overlay.layer * -0.01) - 0.01, max_layer)
|
||||
if (shift_all || i == 1)
|
||||
overlay.pixel_x += offsets.x
|
||||
overlay.pixel_y += offsets.y
|
||||
overlay.pixel_z += 11
|
||||
return overlays
|
||||
|
||||
///Gets the pixel offsets of the rear part of the golf cart
|
||||
/obj/vehicle/ridden/golfcart/proc/get_rear_offset()
|
||||
var/x = 0
|
||||
var/y = 0
|
||||
if (dir & NORTH)
|
||||
y = -32
|
||||
else if (dir & SOUTH)
|
||||
y = 32
|
||||
else if (dir & EAST)
|
||||
x = -32
|
||||
else if (dir & WEST)
|
||||
x = 32
|
||||
return vector(x, y)
|
||||
|
||||
/obj/vehicle/ridden/golfcart/update_overlays()
|
||||
. = ..()
|
||||
// the overlays for the cart are fairly complicated.
|
||||
// the main three are the front, rear, and lower overlays
|
||||
// the front/rear overlay are the same layer
|
||||
// but the lower overlay is always below buckled mobs and the cargo
|
||||
|
||||
var/mutable_appearance/lower_overlay = mutable_appearance(icon, "lower", CART_LOWER_LAYER)
|
||||
var/mutable_appearance/roof_overlay = null
|
||||
var/mutable_appearance/rear_overlay = mutable_appearance(icon, "rear", layer)
|
||||
var/vector/rear_offsets = get_rear_offset()
|
||||
rear_overlay.pixel_x = rear_offsets.x
|
||||
rear_overlay.pixel_y = rear_offsets.y
|
||||
if (dir & SOUTH)
|
||||
// however, specifically when facing south, we require another overlay.
|
||||
// it is effectively an extension of the lower overlay, but it has to be on a different tile so it has to be a different overlay
|
||||
var/mutable_appearance/floor_overlay = mutable_appearance(icon, "floor", CART_LOWER_LAYER)
|
||||
floor_overlay.pixel_y += 25
|
||||
. += floor_overlay
|
||||
|
||||
lower_overlay.pixel_y = 32
|
||||
|
||||
roof_overlay = mutable_appearance(icon, "roof", CART_ROOF_LAYER)
|
||||
roof_overlay.pixel_y = 32
|
||||
else if (dir & EAST)
|
||||
lower_overlay.pixel_x = -32
|
||||
lower_overlay.layer -= 0.02
|
||||
|
||||
roof_overlay = mutable_appearance(icon, "roof", CART_ROOF_LAYER)
|
||||
roof_overlay.pixel_y = 31
|
||||
roof_overlay.pixel_x = -10
|
||||
else if (dir & WEST)
|
||||
lower_overlay.pixel_x = 32
|
||||
lower_overlay.layer -= 0.02
|
||||
|
||||
roof_overlay = mutable_appearance(icon, "roof", CART_ROOF_LAYER)
|
||||
roof_overlay.pixel_y = 31
|
||||
roof_overlay.pixel_x = 10
|
||||
. += lower_overlay
|
||||
. += rear_overlay
|
||||
if (hood_open)
|
||||
. += mutable_appearance(icon, "hood", layer + 0.01)
|
||||
if (roof_overlay)
|
||||
. += roof_overlay
|
||||
|
||||
if (child.cargo)
|
||||
// the cargo is a seperate vis_overlay so that it can be animate()d
|
||||
vis_contents -= cargo_image
|
||||
cargo_image = null
|
||||
var/vector/offsets = get_cargo_offsets()
|
||||
var/list/overlays = generate_cargo_overlay(max_layer=CARGO_HITBOX_LAYER, shift_all=FALSE)
|
||||
if (overlays.len)
|
||||
var/mutable_appearance/base_overlay = overlays[1]
|
||||
overlays.Remove(base_overlay)
|
||||
cargo_image = SSvis_overlays.add_vis_overlay(src, base_overlay.icon, base_overlay.icon_state, base_overlay.layer, plane, dir)
|
||||
cargo_image.overlays = overlays
|
||||
cargo_image.pixel_x = offsets.x
|
||||
cargo_image.pixel_y = offsets.y
|
||||
cargo_image.pixel_z = 11
|
||||
cargo_image.layer = base_overlay.layer
|
||||
vis_contents += cargo_image
|
||||
else
|
||||
cargo_image = null
|
||||
|
||||
///Allows mobs sitting in the rear of the cart to not shoot either the front of the cart or the driver of the cart.
|
||||
/obj/vehicle/ridden/golfcart/proc/dodge_friendly_fire(mob/source, obj/projectile/projectile)
|
||||
SIGNAL_HANDLER
|
||||
|
||||
if (!projectile.firer)
|
||||
return
|
||||
if (QDELETED(projectile.firer))
|
||||
return
|
||||
// so that you don't murder your driver when shooting off the back
|
||||
if (projectile.firer in child.buckled_mobs)
|
||||
return PROJECTILE_INTERRUPT_HIT_PHASE
|
||||
|
||||
/obj/vehicle/ridden/golfcart/post_buckle_mob(mob/living/M)
|
||||
if (M.pulling)
|
||||
M.stop_pulling()
|
||||
RegisterSignal(M, COMSIG_PROJECTILE_PREHIT, PROC_REF(dodge_friendly_fire))
|
||||
RegisterSignal(M, COMSIG_ATOM_TRIED_PASS, PROC_REF(allow_movement_between_passengers))
|
||||
return ..()
|
||||
|
||||
/obj/vehicle/ridden/golfcart/post_unbuckle_mob(mob/living/M)
|
||||
update_appearance(UPDATE_ICON) // because for some reason the overlays aren't properly redrawn
|
||||
UnregisterSignal(M, COMSIG_ATOM_TRIED_PASS)
|
||||
UnregisterSignal(M, COMSIG_PROJECTILE_PREHIT)
|
||||
return ..()
|
||||
|
||||
/obj/vehicle/ridden/golfcart/Destroy()
|
||||
UnregisterSignal(src, COMSIG_MOVABLE_PRE_MOVE)
|
||||
if (!QDELETED(child))
|
||||
qdel(child)
|
||||
child = null
|
||||
return ..()
|
||||
|
||||
#undef ENGINE_UNWRENCHED
|
||||
#undef ENGINE_WRENCHED
|
||||
#undef ENGINE_WELDED
|
||||
#undef CARGO_HITBOX_LAYER
|
||||
#undef CART_ROOF_LAYER
|
||||
#undef CART_LOWER_LAYER
|
||||
#undef FAKE_GLIDE_INITIAL_SOURCE
|
||||
#undef GOLFCART_RUN_OVER_DAMAGE
|
||||
@@ -0,0 +1,388 @@
|
||||
#define GOLFCART_RIDING_SOURCE "riding_golfcart"
|
||||
#define HUMAN_LOWER_LAYER (MOB_LAYER)
|
||||
#define CARGO_HITBOX_LAYER (ABOVE_ALL_MOB_LAYER)
|
||||
#define BELOW_HUMAN_HITBOX_LAYER (ABOVE_MOB_LAYER + 0.01)
|
||||
#define HUMAN_RIDING_LAYER (BELOW_HUMAN_HITBOX_LAYER + 0.02)
|
||||
|
||||
/obj/golfcart_rear
|
||||
name = "golf cart bed"
|
||||
icon = 'icons/obj/toys/golfcart_split.dmi'
|
||||
icon_state = "rear_hitbox"
|
||||
density = TRUE
|
||||
alpha = 1
|
||||
can_buckle = TRUE
|
||||
max_buckled_mobs = 2
|
||||
glide_size = MAX_GLIDE_SIZE
|
||||
layer = 0
|
||||
///Currently buckled cargo
|
||||
var/obj/cargo = null
|
||||
///Was this move triggered by the parent?
|
||||
var/moving_from_parent = FALSE
|
||||
var/obj/vehicle/ridden/golfcart/parent = null
|
||||
///List of offsets for buckled passengers. Indexed by passenger index, then by direction string.
|
||||
var/static/list/list/vector/passenger_offsets = list(
|
||||
list(
|
||||
TEXT_NORTH = vector(-4, 0, 24),
|
||||
TEXT_SOUTH = vector(4, 0, 4),
|
||||
TEXT_EAST = vector(4, 0, 13),
|
||||
TEXT_WEST = vector(-4, 0, 13)
|
||||
),
|
||||
list(
|
||||
TEXT_NORTH = vector(4, 0, 20),
|
||||
TEXT_SOUTH = vector(-4, 8, 0),
|
||||
TEXT_EAST = vector(-8, 0, 13),
|
||||
TEXT_WEST = vector(8, 0, 13)
|
||||
)
|
||||
)
|
||||
///Same as [/obj/golfcart_rear/passenger_offsets], except for when the passenger is lying down.
|
||||
var/static/list/list/vector/lying_down_passenger_offsets = list(
|
||||
list(
|
||||
TEXT_NORTH = vector(0, 0, 16),
|
||||
TEXT_SOUTH = vector(0, 0, 8),
|
||||
TEXT_EAST = vector(2, 0, 8),
|
||||
TEXT_WEST = vector(-2, 0, 8),
|
||||
)
|
||||
)
|
||||
|
||||
///Try to load something onto the cart. This proc may fail if the obj is not in allowed_cargo or is in banned_cargo.
|
||||
/obj/golfcart_rear/proc/load(obj/to_load)
|
||||
if (!to_load)
|
||||
return
|
||||
if (cargo)
|
||||
return
|
||||
if (to_load.anchored)
|
||||
return
|
||||
if (to_load.has_buckled_mobs())
|
||||
// can't stack buckles and whatever
|
||||
return
|
||||
if (istype(to_load, /obj/structure/closet))
|
||||
var/obj/structure/closet/crate = to_load
|
||||
crate.close()
|
||||
to_load.forceMove(src)
|
||||
cargo = to_load
|
||||
layer = CARGO_HITBOX_LAYER
|
||||
parent.update_appearance(UPDATE_ICON)
|
||||
|
||||
/obj/golfcart_rear/proc/unload()
|
||||
if (!cargo)
|
||||
return
|
||||
var/list/candidates = list(
|
||||
get_step(src, turn(dir, 180)),
|
||||
get_step(src, turn(dir, 90)),
|
||||
get_step(src, turn(dir, 270)),
|
||||
)
|
||||
var/atom/dropoff = get_turf(src)
|
||||
for (var/atom/turf in candidates)
|
||||
if (turf.Enter(cargo, src))
|
||||
dropoff = turf
|
||||
break
|
||||
cargo.forceMove(dropoff)
|
||||
cargo = null
|
||||
layer = BELOW_HUMAN_HITBOX_LAYER
|
||||
parent.update_appearance(UPDATE_ICON)
|
||||
|
||||
///Jiggles the cargo_image as long as someone is trying to jiggle it.
|
||||
/obj/golfcart_rear/proc/check_if_shake()
|
||||
if (!cargo)
|
||||
return FALSE
|
||||
|
||||
// Assuming we decide to shake again, how long until we check to shake again
|
||||
var/next_check_time = 0.75 SECONDS
|
||||
|
||||
// How long we shake between different calls of Shake(), so that it starts shaking and stops, instead of a steady shake
|
||||
var/shake_duration = 0.125 SECONDS
|
||||
|
||||
for(var/mob/living/mob in cargo.contents)
|
||||
if(DOING_INTERACTION_WITH_TARGET(mob, src))
|
||||
// Shake and queue another check_if_shake
|
||||
parent.shake_cargo(1, 6, shake_duration)
|
||||
addtimer(CALLBACK(src, PROC_REF(check_if_shake)), next_check_time)
|
||||
return TRUE
|
||||
|
||||
// If we reach here, nobody is resisting, so don't shake
|
||||
return FALSE
|
||||
|
||||
/obj/golfcart_rear/proc/after_escape(obj/container, mob/living/user)
|
||||
user?.visible_message(
|
||||
span_danger("The [container] falls off of the [src]!"),
|
||||
span_userdanger("You knock the crate off the [src]!")
|
||||
)
|
||||
container.SpinAnimation(5, 1)
|
||||
if (user && istype(container, /obj/structure/closet))
|
||||
var/obj/structure/closet/closet = container
|
||||
if (closet.can_open(user))
|
||||
closet.open()
|
||||
|
||||
///Unload the container from the golfcart if it is cargo
|
||||
/obj/golfcart_rear/proc/easy_escape(mob/living/user, obj/container)
|
||||
if (!cargo || cargo != container)
|
||||
return
|
||||
unload()
|
||||
after_escape(container, user)
|
||||
|
||||
///Unload the container from the golfcart if it is cargo and after a little jiggling and a some time
|
||||
/obj/golfcart_rear/proc/hard_escape(mob/living/user, obj/container)
|
||||
addtimer(CALLBACK(src, PROC_REF(check_if_shake)), 0)
|
||||
if (do_after(user, 5 SECONDS, target=src, timed_action_flags=IGNORE_USER_LOC_CHANGE))
|
||||
if (!cargo || cargo != container || !(user in cargo))
|
||||
return
|
||||
unload()
|
||||
after_escape(container, user)
|
||||
|
||||
///Called when someone resists inside of the cargo hitch.
|
||||
/obj/golfcart_rear/relay_container_resist_act(mob/living/user, obj/container)
|
||||
user.visible_message(
|
||||
span_danger("[user] tries to escape the [container]!"),
|
||||
span_userdanger("You try to escape the [container]!"),
|
||||
)
|
||||
if (parent.has_buckled_mobs())
|
||||
for (var/mob/driver in parent.buckled_mobs)
|
||||
if (!parent.is_driver(driver))
|
||||
continue
|
||||
driver.show_message(span_userdanger("The [container] shakes violently!"))
|
||||
if (istype(container, /obj/structure/closet))
|
||||
var/obj/structure/closet/closet = container
|
||||
if (!closet.welded)
|
||||
return easy_escape(user, container)
|
||||
return hard_escape(user, container)
|
||||
return easy_escape(user, container)
|
||||
|
||||
/obj/golfcart_rear/take_damage(damage_amount, damage_type, damage_flag, sound_effect, attack_dir, armour_penetration)
|
||||
if (!parent)
|
||||
return
|
||||
return parent.take_damage(damage_amount, damage_type, damage_flag, sound_effect, attack_dir, armour_penetration)
|
||||
|
||||
/obj/golfcart_rear/attack_hand(mob/user, list/modifiers)
|
||||
if(!isnull(cargo))
|
||||
unload()
|
||||
return TRUE
|
||||
return ..()
|
||||
|
||||
/obj/golfcart_rear/crowbar_act(mob/living/user, obj/item/tool)
|
||||
. = ..()
|
||||
if (!cargo)
|
||||
return
|
||||
tool.play_tool_sound(src, 50)
|
||||
unload()
|
||||
return ITEM_INTERACT_SUCCESS
|
||||
|
||||
/obj/golfcart_rear/proc/can_load(thing)
|
||||
return is_type_in_typecache(thing, parent.allowed_cargo) && (!is_type_in_typecache(thing, parent.banned_cargo)) && (!has_buckled_mobs())
|
||||
|
||||
/obj/golfcart_rear/mouse_drop_receive(atom/dropped, mob/user, params)
|
||||
if (!can_load(dropped))
|
||||
if (has_buckled_mobs())
|
||||
balloon_alert(user, "blocked!")
|
||||
return ..()
|
||||
var/obj/dropped_obj = dropped
|
||||
return load(dropped_obj)
|
||||
|
||||
/obj/golfcart_rear/examine(mob/user)
|
||||
if (!parent)
|
||||
. = ..()
|
||||
. += span_warning("A lone golf cart bed must be a bad omen...")
|
||||
return
|
||||
return parent.examine(user)
|
||||
|
||||
/obj/golfcart_rear/examine_more(mob/user)
|
||||
if (!parent)
|
||||
return ..()
|
||||
return parent.examine_more(user)
|
||||
|
||||
///Called if the rear of the golfcart MUST move to destination and must NOT notify the parent about it.
|
||||
/obj/golfcart_rear/proc/move_from_parent(atom/destination)
|
||||
moving_from_parent = TRUE
|
||||
currently_z_moving = destination.z != loc.z
|
||||
. = forceMove(destination)
|
||||
moving_from_parent = FALSE
|
||||
|
||||
/obj/golfcart_rear/doMove(atom/destination)
|
||||
. = ..()
|
||||
if (!moving_from_parent)
|
||||
return
|
||||
for (var/mob/buckled_mob in buckled_mobs)
|
||||
if (currently_z_moving)
|
||||
buckled_mob.currently_z_moving = currently_z_moving
|
||||
buckled_mob.forceMove(destination)
|
||||
else
|
||||
// this is not a good hack - this should never happen
|
||||
// but stairs are a particularly problematic area
|
||||
if (!buckled_mob.Move(destination, dir, glide_size))
|
||||
// this is a terrible hack because mob/living forwards forceMove calls to buckled
|
||||
// unless currently_z_moving is non-null
|
||||
buckled_mob.currently_z_moving = CURRENTLY_Z_MOVING_GENERIC
|
||||
buckled_mob.forceMove(destination)
|
||||
buckled_mob.currently_z_moving = FALSE
|
||||
|
||||
|
||||
/obj/golfcart_rear/CanAllowThrough(atom/movable/mover, border_dir)
|
||||
. = ..()
|
||||
if (!parent)
|
||||
return
|
||||
if (mover == parent)
|
||||
return TRUE
|
||||
if (mover in parent.buckled_mobs)
|
||||
return TRUE
|
||||
if (parent.allow_crawler_through(mover))
|
||||
return TRUE
|
||||
|
||||
/obj/golfcart_rear/Move(atom/newloc, direct, glide_size_override = 0, update_dir = TRUE)
|
||||
if (moving_from_parent)
|
||||
return
|
||||
|
||||
if(pulledby)
|
||||
var/olddir = dir
|
||||
var/newdir
|
||||
if (direct & (EAST | WEST))
|
||||
newdir = (direct & EAST) ? EAST : WEST
|
||||
else if (direct & (NORTH | SOUTH))
|
||||
newdir = (direct & NORTH) ? NORTH : SOUTH
|
||||
else
|
||||
newdir = direct
|
||||
set_glide_size(glide_size_override ? glide_size_override : pulledby.glide_size)
|
||||
. = ..()
|
||||
dir = newdir
|
||||
if (get_step(src, turn(dir, 180)) != get_turf(pulledby))
|
||||
setDir(turn(dir, 180))
|
||||
var/atom/behind = get_step(src, dir)
|
||||
if (!behind.Enter(parent))
|
||||
setDir(olddir)
|
||||
behind = get_step(src, dir)
|
||||
parent.set_glide_size(glide_size_override ? glide_size_override : pulledby.glide_size)
|
||||
parent.forceMove(behind)
|
||||
parent.setDir(dir)
|
||||
parent.update_appearance(UPDATE_ICON)
|
||||
return
|
||||
|
||||
return parent.Move(get_step(parent, get_dir(loc, newloc)), direct)
|
||||
|
||||
///Called for COMSIG_ATOM_TRIED_PASS on passengers buckled to the cart. Allows them to not block each other's movement / get blocked by the cart.
|
||||
/obj/golfcart_rear/proc/allow_movement_between_bed_passengers(atom/source, atom/mover)
|
||||
SIGNAL_HANDLER
|
||||
|
||||
if (mover == parent)
|
||||
return COMSIG_COMPONENT_PERMIT_PASSAGE
|
||||
if (parent && (mover in parent.buckled_mobs))
|
||||
return COMSIG_COMPONENT_PERMIT_PASSAGE
|
||||
if ((source in buckled_mobs) && (mover in buckled_mobs))
|
||||
return COMSIG_COMPONENT_PERMIT_PASSAGE
|
||||
|
||||
///Called when the golfcart rear turns in order to keep the buckled mobs in the right places
|
||||
/obj/golfcart_rear/proc/update_passenger_layers(new_dir)
|
||||
if (isnull(new_dir))
|
||||
new_dir = dir
|
||||
var/layer = HUMAN_RIDING_LAYER
|
||||
var/invert_layer = FALSE
|
||||
if (new_dir & SOUTH)
|
||||
invert_layer = TRUE
|
||||
new_dir = "[new_dir]"
|
||||
for(var/i in 1 to buckled_mobs.len)
|
||||
var/mob/living/passenger = buckled_mobs[i]
|
||||
var/vector/offset
|
||||
if (passenger.body_position == LYING_DOWN)
|
||||
offset = lying_down_passenger_offsets[i][new_dir]
|
||||
else
|
||||
offset = passenger_offsets[i][new_dir]
|
||||
passenger.add_offsets(GOLFCART_RIDING_SOURCE,
|
||||
x_add = offset.x,
|
||||
y_add = offset.y,
|
||||
z_add = offset.z,
|
||||
animate = FALSE)
|
||||
passenger.layer = layer + ((i * 0.01) - 0.01) * (-invert_layer)
|
||||
|
||||
///Called from COMSIG_ATOM_POST_DIR_CHANGE on the rear of the cart. Only used to change buckled mobs' position / layers.
|
||||
/obj/golfcart_rear/proc/on_dir_changed(datum/source, old_dir, new_dir)
|
||||
SIGNAL_HANDLER
|
||||
|
||||
if (!has_buckled_mobs())
|
||||
return
|
||||
update_passenger_layers(new_dir)
|
||||
|
||||
/obj/golfcart_rear/Initialize(mapload, obj/vehicle/ridden/golfcart/progenitor)
|
||||
. = ..()
|
||||
parent = progenitor
|
||||
layer = BELOW_HUMAN_HITBOX_LAYER
|
||||
RegisterSignal(parent, COMSIG_ATOM_POST_DIR_CHANGE, PROC_REF(on_dir_changed))
|
||||
|
||||
/obj/golfcart_rear/update_overlays()
|
||||
. = ..()
|
||||
if (dir & NORTH)
|
||||
var/mutable_appearance/hitbox_overlay = mutable_appearance(icon, "rear_hitbox_overlay", layer)
|
||||
hitbox_overlay.pixel_y += 32
|
||||
. += hitbox_overlay
|
||||
else if (dir & SOUTH)
|
||||
. += mutable_appearance(icon, "rear_hitbox_lower", OBJ_LAYER + 0.01)
|
||||
if(!cargo)
|
||||
return
|
||||
var/vector/rear_offsets = parent.get_rear_offset()
|
||||
. += parent.generate_cargo_overlay(-rear_offsets.x, -rear_offsets.y, layer=layer)
|
||||
|
||||
///Called when a passenger tries lying down/getting up. Automatically drops out people who can't stay on
|
||||
/obj/golfcart_rear/proc/passenger_falling_down(atom/source, new_bodypos)
|
||||
SIGNAL_HANDLER
|
||||
|
||||
if (!isliving(source))
|
||||
return // should runtime?
|
||||
if (new_bodypos == STANDING_UP)
|
||||
update_passenger_layers()
|
||||
return
|
||||
if (buckled_mobs.len <= 1)
|
||||
update_passenger_layers()
|
||||
return // allow 1 laying down mob
|
||||
var/mob/living/passenger = source
|
||||
unbuckle_mob(passenger, TRUE)
|
||||
|
||||
/obj/golfcart_rear/is_buckle_possible(mob/living/target, force, check_loc)
|
||||
. = ..()
|
||||
// these are to_viewers because you can buckle someone on their behalf
|
||||
if (parent && cargo)
|
||||
balloon_alert_to_viewers("blocked!")
|
||||
return FALSE
|
||||
if (target.body_position != STANDING_UP)
|
||||
if (!has_buckled_mobs())
|
||||
return TRUE
|
||||
balloon_alert_to_viewers("stand up!")
|
||||
return FALSE
|
||||
for (var/mob/blocker in buckled_mobs)
|
||||
if (!isliving(blocker))
|
||||
balloon_alert_to_viewers("blocked!")
|
||||
return FALSE
|
||||
var/mob/living/living_blocker = blocker
|
||||
if (living_blocker.body_position != STANDING_UP)
|
||||
balloon_alert_to_viewers("blocked!")
|
||||
return FALSE
|
||||
return TRUE
|
||||
|
||||
/obj/golfcart_rear/post_buckle_mob(mob/living/buckled_mob)
|
||||
buckled_mob.pulledby?.stop_pulling()
|
||||
RegisterSignal(buckled_mob, COMSIG_ATOM_TRIED_PASS, PROC_REF(allow_movement_between_bed_passengers))
|
||||
RegisterSignal(buckled_mob, COMSIG_LIVING_SET_BODY_POSITION, PROC_REF(passenger_falling_down))
|
||||
. = ..()
|
||||
update_passenger_layers()
|
||||
|
||||
/obj/golfcart_rear/post_unbuckle_mob(mob/living/buckled_mob)
|
||||
UnregisterSignal(buckled_mob, COMSIG_ATOM_TRIED_PASS)
|
||||
UnregisterSignal(buckled_mob, COMSIG_LIVING_SET_BODY_POSITION)
|
||||
buckled_mob.remove_offsets(GOLFCART_RIDING_SOURCE)
|
||||
if (buckled_mob.body_position == LYING_DOWN)
|
||||
buckled_mob.layer = LYING_MOB_LAYER
|
||||
else
|
||||
buckled_mob.layer = initial(buckled_mob.layer)
|
||||
return ..()
|
||||
|
||||
/obj/golfcart_rear/Destroy()
|
||||
if (parent)
|
||||
UnregisterSignal(parent, COMSIG_ATOM_POST_DIR_CHANGE)
|
||||
if (!QDELETED(parent))
|
||||
qdel(parent)
|
||||
parent = null
|
||||
if (cargo && !QDELETED(cargo))
|
||||
cargo.forceMove(drop_location())
|
||||
cargo = null
|
||||
return ..()
|
||||
|
||||
#undef CARGO_HITBOX_LAYER
|
||||
#undef HUMAN_LOWER_LAYER
|
||||
#undef HUMAN_RIDING_LAYER
|
||||
#undef GOLFCART_RIDING_SOURCE
|
||||
#undef BELOW_HUMAN_HITBOX_LAYER
|
||||
@@ -416,6 +416,81 @@
|
||||
initialize_controller_action_type(/datum/action/vehicle/sealed/mecha/mech_view_stats, VEHICLE_CONTROL_SETTINGS)
|
||||
initialize_controller_action_type(/datum/action/vehicle/sealed/mecha/strafe, VEHICLE_CONTROL_DRIVE)
|
||||
|
||||
/obj/vehicle/sealed/mecha/add_occupant(mob/M, control_flags, forced)
|
||||
if(..())
|
||||
generate_equipment_actions(M)
|
||||
|
||||
/obj/vehicle/sealed/mecha/remove_occupant(mob/M)
|
||||
remove_all_equipment_actions(M)
|
||||
return ..()
|
||||
|
||||
///Generates action buttons for all eligible equipment and grants them to the occupant with VEHICLE_CONTROL_SETTINGS flag.
|
||||
/obj/vehicle/sealed/mecha/proc/generate_equipment_actions(mob/occupant)
|
||||
if(!(occupant in occupants) || !(occupants[occupant] & VEHICLE_CONTROL_SETTINGS))
|
||||
return
|
||||
for(var/obj/item/mecha_parts/mecha_equipment/equipment in flat_equipment)
|
||||
if(!is_equipment_valid_for_action(equipment))
|
||||
continue
|
||||
|
||||
grant_equipment_action(occupant, equipment)
|
||||
|
||||
///Removes all equipment actions from a specific occupant.
|
||||
/obj/vehicle/sealed/mecha/proc/remove_all_equipment_actions(mob/occupant)
|
||||
var/list/actions = LAZYACCESS(occupant_actions, occupant)
|
||||
if(!actions)
|
||||
return
|
||||
|
||||
for(var/equipment_type in actions)
|
||||
if(!ispath(equipment_type, /obj/item/mecha_parts/mecha_equipment))
|
||||
continue
|
||||
|
||||
remove_action_type_from_mob(equipment_type, occupant)
|
||||
|
||||
/**
|
||||
* Grants a specific equipment action to an occupant.
|
||||
* Creates a new action, sets up the chassis and equipment references, and grants it to the mob.
|
||||
*/
|
||||
/obj/vehicle/sealed/mecha/proc/grant_equipment_action(mob/occupant, obj/item/mecha_parts/mecha_equipment/equipment)
|
||||
var/datum/action/vehicle/sealed/mecha/equipment/action = new // We cannot use grant_action_type_to_mob() because:
|
||||
action.set_chassis(src) // 1. grant_action_type_to_mob() works with a single predefined action type
|
||||
action.set_equipment(equipment) // 2. We create unique action instances for each equipment with specific equipment references
|
||||
|
||||
action.Grant(occupant)
|
||||
LAZYINITLIST(occupant_actions[occupant])
|
||||
// Use equipment type as actiontype for remove_action_type_from_mob() compatibility
|
||||
occupant_actions[occupant][equipment.type] = action
|
||||
|
||||
/**
|
||||
* Called when equipment is attached to the mecha.
|
||||
* Grants equipment actions to current occupants with VEHICLE_CONTROL_SETTINGS flag.
|
||||
*/
|
||||
/obj/vehicle/sealed/mecha/proc/on_equipment_attach(obj/item/mecha_parts/mecha_equipment/equipment)
|
||||
if(!is_equipment_valid_for_action(equipment))
|
||||
return
|
||||
|
||||
for(var/mob/occupant in occupants)
|
||||
if(!(occupants[occupant] & VEHICLE_CONTROL_SETTINGS))
|
||||
continue
|
||||
grant_equipment_action(occupant, equipment)
|
||||
|
||||
/**
|
||||
* Called when equipment is detached from the mecha.
|
||||
* Removes equipment actions from all current occupants.
|
||||
*/
|
||||
/obj/vehicle/sealed/mecha/proc/on_equipment_detach(obj/item/mecha_parts/mecha_equipment/equipment)
|
||||
for(var/mob/occupant in occupants)
|
||||
remove_action_type_from_mob(equipment.type, occupant)
|
||||
|
||||
/// Create actions only for equipment that can be toggled or triggered, excluding air tanks.
|
||||
/obj/vehicle/sealed/mecha/proc/is_equipment_valid_for_action(obj/item/mecha_parts/mecha_equipment/equipment)
|
||||
if(!(equipment.can_be_toggled || equipment.can_be_triggered))
|
||||
return FALSE
|
||||
|
||||
if(istype(equipment, /obj/item/mecha_parts/mecha_equipment/air_tank)) // this thing has its own button
|
||||
return FALSE
|
||||
|
||||
return TRUE
|
||||
|
||||
/obj/vehicle/sealed/mecha/proc/get_mecha_occupancy_state()
|
||||
if((mecha_flags & SILICON_PILOT) && silicon_icon_state)
|
||||
return silicon_icon_state
|
||||
|
||||
@@ -214,6 +214,7 @@
|
||||
SEND_SIGNAL(src, COMSIG_MECHA_EQUIPMENT_ATTACHED)
|
||||
forceMove(new_mecha)
|
||||
log_message("[src] initialized.", LOG_MECHA)
|
||||
chassis.on_equipment_attach(src)
|
||||
|
||||
/**
|
||||
* called to detach this equipment
|
||||
@@ -221,6 +222,7 @@
|
||||
* * moveto: optional target to move this equipment to
|
||||
*/
|
||||
/obj/item/mecha_parts/mecha_equipment/proc/detach(atom/moveto)
|
||||
chassis.on_equipment_detach(src)
|
||||
moveto = moveto || get_turf(chassis)
|
||||
forceMove(moveto)
|
||||
playsound(chassis, 'sound/items/weapons/tap.ogg', 50, TRUE)
|
||||
|
||||
@@ -273,9 +273,7 @@
|
||||
to_chat(M, "<font color='red' size='7'>HONK</font>")
|
||||
M.SetSleeping(0)
|
||||
M.adjust_stutter(40 SECONDS)
|
||||
var/obj/item/organ/ears/ears = M.get_organ_slot(ORGAN_SLOT_EARS)
|
||||
if(ears)
|
||||
ears.adjustEarDamage(0, 30)
|
||||
M.sound_damage(deafen = 30 SECONDS)
|
||||
M.Paralyze(60)
|
||||
if(prob(30))
|
||||
M.Stun(200)
|
||||
|
||||
@@ -144,10 +144,12 @@
|
||||
chassis.balloon_alert(owner, "controlling gunner seat")
|
||||
chassis.remove_control_flags(owner, VEHICLE_CONTROL_DRIVE|VEHICLE_CONTROL_SETTINGS)
|
||||
chassis.add_control_flags(owner, VEHICLE_CONTROL_MELEE|VEHICLE_CONTROL_EQUIPMENT)
|
||||
chassis.remove_all_equipment_actions(owner)
|
||||
else
|
||||
chassis.balloon_alert(owner, "controlling pilot seat")
|
||||
chassis.remove_control_flags(owner, VEHICLE_CONTROL_MELEE|VEHICLE_CONTROL_EQUIPMENT)
|
||||
chassis.add_control_flags(owner, VEHICLE_CONTROL_DRIVE|VEHICLE_CONTROL_SETTINGS)
|
||||
chassis.generate_equipment_actions(owner)
|
||||
chassis.update_icon_state()
|
||||
|
||||
/datum/action/vehicle/sealed/mecha/mech_overclock
|
||||
@@ -162,3 +164,33 @@
|
||||
chassis.toggle_overclock(forced_state)
|
||||
button_icon_state = "mech_overload_[chassis.overclock_mode ? "on" : "off"]"
|
||||
build_all_button_icons()
|
||||
|
||||
/datum/action/vehicle/sealed/mecha/equipment
|
||||
name = "Mech Equipment"
|
||||
button_icon_state = null
|
||||
background_icon_state = "bg_tech"
|
||||
var/obj/item/mecha_parts/mecha_equipment/equipment
|
||||
|
||||
/datum/action/vehicle/sealed/mecha/equipment/Destroy()
|
||||
equipment = null
|
||||
return ..()
|
||||
|
||||
/datum/action/vehicle/sealed/mecha/equipment/Trigger(mob/clicker, trigger_flags)
|
||||
if(!..())
|
||||
return
|
||||
if(!chassis || !(owner in chassis.occupants) || !equipment)
|
||||
return
|
||||
|
||||
equipment.set_active(!equipment.active)
|
||||
equipment.handle_ui_act(action = "toggle")
|
||||
chassis.balloon_alert(owner, "[equipment.name] [equipment.active ? "on" : "off"]!")
|
||||
|
||||
/datum/action/vehicle/sealed/mecha/equipment/proc/set_equipment(passed_equipment)
|
||||
equipment = passed_equipment
|
||||
name = "Toggle [equipment.name]"
|
||||
desc = equipment.desc
|
||||
target = equipment
|
||||
if(target)
|
||||
AddComponent(/datum/component/action_item_overlay, equipment)
|
||||
|
||||
build_button_icon()
|
||||
|
||||
Reference in New Issue
Block a user