Adds a golf cart to cargo (#92688)

This commit is contained in:
tonyhawq
2025-10-21 16:07:20 -04:00
committed by GitHub
parent fff3ac935a
commit e5d40d3cfe
8 changed files with 1136 additions and 0 deletions
@@ -475,3 +475,60 @@
var/obj/vehicle/ridden/wheelchair/motorized/our_chair = parent
if(istype(our_chair) && our_chair.power_cell)
our_chair.power_cell.use(our_chair.energy_usage / max(our_chair.power_efficiency, 1) * 0.05)
/datum/component/riding/vehicle/golfcart
ride_check_flags = RIDER_NEEDS_ARMS | UNBUCKLE_DISABLED_RIDER
vehicle_move_delay = 1.5
keytype = /obj/item/key/golfcart
/datum/component/riding/vehicle/golfcart/restore_parent_layer_and_offsets()
// just don't restore anything.
// restoring layers fucks stuff to do with the rear part
return
/datum/component/riding/vehicle/golfcart/driver_move(atom/movable/movable_parent, mob/living/user, direction)
if (!istype(parent, /obj/vehicle/ridden/golfcart))
return ..()
var/obj/vehicle/ridden/golfcart/cart = parent
if (!cart.cell && !cart.is_hotrod())
return COMPONENT_DRIVER_BLOCK_MOVE
if (cart.cell)
if (cart.cell.charge <= 0)
return COMPONENT_DRIVER_BLOCK_MOVE
if (get_turf(cart.child) == get_step(cart, direction))
cart.set_movedelay_effect(2)
else
cart.set_movedelay_effect(1)
vehicle_move_delay = cart.movedelay
return ..()
/datum/component/riding/vehicle/golfcart/handle_ride(mob/user, direction)
if (!istype(parent, /obj/vehicle/ridden/golfcart))
return ..()
var/obj/vehicle/ridden/golfcart/cart = parent
if (cart.cell)
var/charge_to_use = min(cart.charge_per_move, cart.cell.charge)
cart.cell.use(charge_to_use)
return ..()
/datum/component/riding/vehicle/golfcart/update_parent_layer_and_offsets(dir, animate)
. = ..()
if (istype(parent, /obj))
var/obj/objectified = parent
objectified.update_appearance(UPDATE_ICON)
/datum/component/riding/vehicle/golfcart/get_rider_offsets_and_layers(pass_index, mob/offsetter)
return list(
TEXT_NORTH = list(0, -16),
TEXT_SOUTH = list(0, 10),
TEXT_EAST = list(-8, 2),
TEXT_WEST = list(8, 2),
)
/datum/component/riding/vehicle/golfcart/get_parent_offsets_and_layers()
return list(
TEXT_NORTH = list(0, 0, ABOVE_MOB_LAYER),
TEXT_SOUTH = list(0, 0, ABOVE_MOB_LAYER),
TEXT_EAST = list(0, 0, VEHICLE_LAYER),
TEXT_WEST = list(0, 0, VEHICLE_LAYER),
)
+6
View File
@@ -412,3 +412,9 @@
/obj/item/circuitboard/computer/shuttle/docker,
/obj/item/circuitboard/machine/engine/propulsion = 2,
)
/datum/supply_pack/goody/golfcart_key
name = "Spare Golf Cart Key"
desc = "If you in your carelessness lost the key to your golfcart you can purchase one. Unfortunately not covered by warranty."
cost = PAYCHECK_CREW * 5
contains = list(/obj/item/key/golfcart)
+10
View File
@@ -334,3 +334,13 @@
contains = list(/obj/machinery/power/portagrav = 1)
crate_name = "portable gravity unit crate"
crate_type = /obj/structure/closet/crate/engineering
/datum/supply_pack/engineering/golfcart
name = "Golf Cart Parts Kit Crate"
desc = "Contains the parts to build a cart intended for moving heavy machinery and cargo across the station. \
Nanotrasen assumes no liability for carts operated as 'party wagons'."
cost = CARGO_CRATE_VALUE * 11
access_view = ACCESS_ENGINEERING
contains = list(/obj/item/golfcart_kit = 1, /obj/item/key/golfcart = 2, /obj/item/stock_parts/power_store/cell/lead = 1)
crate_name = "golf cart parts kit"
crate_type = /obj/structure/closet/crate/engineering
@@ -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
Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.6 KiB

+2
View File
@@ -6506,6 +6506,8 @@
#include "code\modules\vehicles\cars\clowncar.dm"
#include "code\modules\vehicles\cars\speedwagon.dm"
#include "code\modules\vehicles\cars\vim.dm"
#include "code\modules\vehicles\cars\golfcart\golfcart.dm"
#include "code\modules\vehicles\cars\golfcart\golfcart_rear.dm"
#include "code\modules\vehicles\mecha\_mecha.dm"
#include "code\modules\vehicles\mecha\mech_bay.dm"
#include "code\modules\vehicles\mecha\mech_fabricator.dm"