mirror of
https://github.com/Aurorastation/Aurora.3.git
synced 2026-07-21 12:56:10 +01:00
387cee9243
Extends and reworks how various extended information text (desc_info, desc_build, desc_upgrades) are handled to make object interactions and mechanics A.) much more clearly documented in-game and B.) much easier to support from the back-end. Almost certainly a candidate for test merge. Assembly/Disassembly instructions are noticeably sporadic, largely due to our current lack of a unified framework. That's a future thing I'd like to attack so that it can be handled programmatically, but for now I only targeted the biggest culprits as I came across them. --------- Signed-off-by: Batrachophreno <Batrochophreno@gmail.com>
523 lines
15 KiB
Plaintext
523 lines
15 KiB
Plaintext
/**
|
|
* # Howitzer
|
|
*
|
|
* Howitzers are a field weapon capable of delivering artillery fire to a location
|
|
*/
|
|
ABSTRACT_TYPE(/obj/machinery/howitzer)
|
|
name = "howitzer"
|
|
|
|
icon = 'icons/obj/machinery/howitzer/howitzer.dmi'
|
|
icon_state = "howitzer_deployed"
|
|
dir = NORTH
|
|
|
|
//Big thing
|
|
w_class = WEIGHT_CLASS_HUGE
|
|
|
|
//Can't pass through it
|
|
density = TRUE
|
|
|
|
//Can't move it around
|
|
anchored = TRUE
|
|
|
|
//Align the icon with the turf
|
|
pixel_x = -16
|
|
|
|
/**
|
|
* List of allowed ammo types that this howitzer will accept
|
|
*/
|
|
var/allowed_ammo_types = list(/obj/item/ammo_casing)
|
|
|
|
///The targeted horizontal angle requested to this howitzer
|
|
var/horizontal_angle = 0
|
|
|
|
///The targeted vertical angle requested to this howitzer
|
|
var/vertical_angle = 0
|
|
|
|
///The deciseconds it takes for the howitzer to rotate by one degree, horizontally
|
|
var/decisecond_per_degree_horizontal_rotation = 1
|
|
|
|
///The deciseconds it takes for the howitzer to change the barrel elevation by one degree, vertically
|
|
var/decisecond_per_degree_vertical_rotation = 1.5
|
|
|
|
///The shot that is currently loaded into this howitzer
|
|
var/obj/item/ammo_casing/howitzer/loaded_shot
|
|
|
|
///The sound to play when the howitzer fire, a string
|
|
var/fire_sound = 'sound/machines/howitzer/fire.ogg'
|
|
|
|
///If the howitzer is ready to fire, handled internally
|
|
VAR_PRIVATE/ready_to_fire = FALSE
|
|
|
|
///A list of `/obj/item/howitzer_pellet` that are currently loaded into this howitzer
|
|
var/list/obj/item/howitzer_pellet/loaded_howitzer_pellets = list()
|
|
|
|
|
|
/* START ROTATION INTERNALS */
|
|
|
|
///The sound this howitzer will emit while rotating
|
|
var/rotation_looping_sound_type = /datum/looping_sound/clanking
|
|
|
|
///The looping sound used during the howitzer rotation
|
|
VAR_PRIVATE/datum/looping_sound/rotation_looping_sound
|
|
|
|
///The timerid of the rotation
|
|
VAR_PRIVATE/rotation_timerid
|
|
|
|
/* END ROTATION INTERNALS */
|
|
|
|
/obj/machinery/howitzer/Initialize(mapload, d, populate_components, is_internal)
|
|
. = ..()
|
|
if(!anchored)
|
|
icon_state = "howitzer"
|
|
|
|
set_dir(NORTH)
|
|
|
|
/obj/machinery/howitzer/attack_hand(mob/user)
|
|
ui_interact(user)
|
|
|
|
/obj/machinery/howitzer/AltClick(mob/user)
|
|
if(ishuman(user))
|
|
var/mob/living/carbon/human/user_human = user
|
|
|
|
if(loaded_shot)
|
|
//Remove the pellets first, the projectile last
|
|
if(length(loaded_howitzer_pellets))
|
|
var/obj/item/howitzer_pellet/pellet = loaded_howitzer_pellets[length(loaded_howitzer_pellets)]
|
|
pellet.forceMove(src)
|
|
user_human.put_in_hands(pellet)
|
|
loaded_howitzer_pellets -= pellet
|
|
|
|
else
|
|
loaded_shot.forceMove(src)
|
|
user_human.put_in_hands(loaded_shot)
|
|
loaded_shot = null
|
|
return
|
|
|
|
/obj/machinery/howitzer/ui_interact(mob/user, datum/tgui/ui)
|
|
ui = SStgui.try_update_ui(user, src, ui)
|
|
if(!ui)
|
|
ui = new(user, src, "Howitzer", ui_x=500, ui_y=380)
|
|
ui.open()
|
|
|
|
/obj/machinery/howitzer/ui_data(mob/user)
|
|
var/list/data = list()
|
|
data["loaded_shot"] = loaded_shot ? TRUE : FALSE
|
|
data["horizontal_angle"] = horizontal_angle
|
|
data["vertical_angle"] = vertical_angle
|
|
|
|
return data
|
|
|
|
/obj/machinery/howitzer/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state)
|
|
. = ..()
|
|
if(.)
|
|
return
|
|
|
|
switch(action)
|
|
if("set_horizontal_angle")
|
|
change_horizontal_angle(params["horizontal_angle"])
|
|
if("set_vertical_angle")
|
|
change_vertical_angle(params["vertical_angle"])
|
|
|
|
if("fire")
|
|
fire(ui.user)
|
|
|
|
/obj/machinery/howitzer/attackby(obj/item/attacking_item, mob/user)
|
|
. = ..()
|
|
|
|
if(istype(attacking_item, /obj/item/ammo_casing))
|
|
//Check if the ammo is allowed
|
|
if(!is_type_in_list(attacking_item, allowed_ammo_types))
|
|
to_chat(user, SPAN_WARNING("This howitzer does not accept this type of ammo."))
|
|
return
|
|
|
|
//Only one shot at a time
|
|
if(loaded_shot)
|
|
to_chat(user, SPAN_WARNING("You cannot load another ammo into this howitzer while it is already loaded."))
|
|
return
|
|
|
|
//Take a little bit to load the ammo
|
|
if(!do_after(user, 10 SECONDS, src))
|
|
return
|
|
|
|
loaded_shot = attacking_item
|
|
user.drop_item(attacking_item)
|
|
attacking_item.forceMove(src)
|
|
|
|
else if(istype(attacking_item, /obj/item/howitzer_pellet))
|
|
if(!loaded_shot)
|
|
to_chat(user, SPAN_WARNING("You cannot load a pellet into the howitzer without the ammunition first."))
|
|
return
|
|
|
|
//Take a little bit to load the pellets
|
|
if(!do_after(user, 1 SECONDS, src))
|
|
return
|
|
|
|
user.drop_item(attacking_item)
|
|
attacking_item.forceMove(src)
|
|
loaded_howitzer_pellets += attacking_item
|
|
|
|
|
|
/**
|
|
* Takes care of changing the horizontal angle of the howitzer
|
|
*
|
|
* * angle - The angle to set, a number
|
|
*/
|
|
/obj/machinery/howitzer/proc/change_horizontal_angle(angle)
|
|
if(angle > 360 || angle < 0)
|
|
stack_trace("Invalid horizontal angle!")
|
|
return
|
|
|
|
if(rotation_timerid)
|
|
balloon_alert_to_viewers("already doing an alignment!")
|
|
return
|
|
|
|
//Not the same angle we are at, we have to rotate
|
|
if(horizontal_angle != angle)
|
|
//Adjust the howitzer direction based on the angle
|
|
var/wanted_dir
|
|
if(angle > 315 || angle <= 45)
|
|
wanted_dir = NORTH
|
|
if(angle > 45 && angle <= 135)
|
|
wanted_dir = EAST
|
|
if(angle > 135 && angle <= 225)
|
|
wanted_dir = SOUTH
|
|
if(angle > 225 && angle <= 315)
|
|
wanted_dir = WEST
|
|
|
|
ready_to_fire = FALSE
|
|
|
|
var/degrees_to_rotate = abs((horizontal_angle % 360)-(angle % 360))
|
|
if(degrees_to_rotate > 180)
|
|
degrees_to_rotate = 360 - degrees_to_rotate
|
|
|
|
balloon_alert_to_viewers("rotating...")
|
|
|
|
//Start the rotation looping sound
|
|
rotation_looping_sound = new rotation_looping_sound_type(src)
|
|
rotation_looping_sound.start()
|
|
|
|
var/duration_of_rotation = (degrees_to_rotate * decisecond_per_degree_horizontal_rotation)
|
|
|
|
rotation_timerid = addtimer(CALLBACK(src, PROC_REF(set_horizontal_angle), angle, wanted_dir), duration_of_rotation, TIMER_STOPPABLE|TIMER_UNIQUE)
|
|
|
|
///Sets the horizontal angle and dir, internal use only, do not call this directly
|
|
/obj/machinery/howitzer/proc/set_horizontal_angle(angle, wanted_dir)
|
|
PROTECTED_PROC(TRUE)
|
|
|
|
rotation_timerid = null
|
|
|
|
horizontal_angle = angle
|
|
set_dir(wanted_dir)
|
|
|
|
balloon_alert_to_viewers("finished rotating")
|
|
rotation_looping_sound.stop()
|
|
QDEL_NULL(rotation_looping_sound)
|
|
|
|
ready_to_fire = TRUE
|
|
|
|
/**
|
|
* Takes care of changing the vertical angle of the howitzer
|
|
*
|
|
* * angle - The angle to set, a number
|
|
*/
|
|
/obj/machinery/howitzer/proc/change_vertical_angle(angle)
|
|
if(angle > 90 || angle < 0)
|
|
stack_trace("Invalid vertical angle!")
|
|
return
|
|
|
|
if(rotation_timerid)
|
|
balloon_alert_to_viewers("already doing an alignment!")
|
|
return
|
|
|
|
if(vertical_angle != angle)
|
|
|
|
ready_to_fire = FALSE
|
|
|
|
var/degrees_to_change = abs((vertical_angle % 360)-(angle % 360))
|
|
if(degrees_to_change > 180) //Yes this shouldn't happen, copy paste anyways
|
|
degrees_to_change = 360 - degrees_to_change
|
|
|
|
balloon_alert_to_viewers("changing elevation...")
|
|
|
|
rotation_looping_sound = new rotation_looping_sound_type(src)
|
|
rotation_looping_sound.start()
|
|
|
|
rotation_timerid = addtimer(CALLBACK(src, PROC_REF(set_vertical_angle), angle), (degrees_to_change * decisecond_per_degree_vertical_rotation), TIMER_STOPPABLE|TIMER_UNIQUE)
|
|
|
|
///Sets the vertical angle and dir, internal use only, do not call this directly
|
|
/obj/machinery/howitzer/proc/set_vertical_angle(angle)
|
|
PROTECTED_PROC(TRUE)
|
|
|
|
rotation_timerid = null
|
|
|
|
vertical_angle = angle
|
|
|
|
balloon_alert_to_viewers("finished changing elevation")
|
|
rotation_looping_sound.stop()
|
|
QDEL_NULL(rotation_looping_sound)
|
|
|
|
ready_to_fire = TRUE
|
|
|
|
/**
|
|
* Fires the projectile to the target
|
|
*
|
|
* * user - The `/mob` that requested to fire the projectile
|
|
*/
|
|
/obj/machinery/howitzer/proc/fire(mob/user)
|
|
if(!anchored)
|
|
balloon_alert_to_viewers("not anchored down!")
|
|
to_chat(user, SPAN_WARNING("The howitzer is not anchored down!"))
|
|
return
|
|
|
|
if(horizontal_angle > 360 || vertical_angle > 90)
|
|
return
|
|
|
|
if(!ready_to_fire)
|
|
balloon_alert_to_viewers("not ready to fire!")
|
|
to_chat(user, SPAN_WARNING("The howitzer is not ready to fire, it's adjusting the aim!"))
|
|
return
|
|
|
|
if(!loaded_shot)
|
|
balloon_alert_to_viewers("barrel is empty!")
|
|
to_chat(user, SPAN_WARNING("The barrel is empty!"))
|
|
return
|
|
|
|
var/turf/T = get_turf(src)
|
|
if(!T)
|
|
return
|
|
|
|
//Calculate how much power the pellets add to the shot
|
|
var/fire_power = 10
|
|
for(var/obj/item/howitzer_pellet/pellet in loaded_howitzer_pellets)
|
|
fire_power += pellet.power
|
|
|
|
var/datum/projectile_data/shot_data = projectile_trajectory(T.x, T.y, horizontal_angle, vertical_angle, fire_power)
|
|
|
|
//Use the projectile and the pellets
|
|
var/obj/projectile/shot_projectile = loaded_shot.expend()
|
|
QDEL_LIST(loaded_howitzer_pellets)
|
|
|
|
//Casing already expended, nothing to shoot
|
|
if(!shot_projectile)
|
|
to_chat(user, SPAN_WARNING("The ammunition is already expended."))
|
|
return
|
|
|
|
//Clamp the target so we don't aim to invalid locations
|
|
if(shot_data.dest_x < 1)
|
|
shot_data.dest_x = 1
|
|
if(shot_data.dest_x > world.maxx)
|
|
shot_data.dest_x = world.maxx
|
|
|
|
if(shot_data.dest_y < 1)
|
|
shot_data.dest_y = 1
|
|
if(shot_data.dest_y > world.maxy)
|
|
shot_data.dest_y = world.maxy
|
|
|
|
var/turf/target = locate(shot_data.dest_x, shot_data.dest_y, src.z)
|
|
|
|
//If somehow the target does not exist, stop here
|
|
if(!target)
|
|
stack_trace("Unable to locate the target, somehow.")
|
|
return
|
|
|
|
shot_projectile.preparePixelProjectile(target, get_turf(src))
|
|
shot_projectile.firer = src
|
|
shot_projectile.fired_from = src
|
|
shot_projectile.fire()
|
|
|
|
flick((icon_state + "_fire"), src)
|
|
|
|
//Play fire sound, smoke effect
|
|
playsound(src, fire_sound, 100, TRUE)
|
|
|
|
//Move the smoke forward a little
|
|
var/turf/smoke_position = get_turf(src)
|
|
for(var/_ in 1 to 2)
|
|
var/turf/tmp_smoke_position = get_step(smoke_position, dir)
|
|
if(istype(tmp_smoke_position))
|
|
smoke_position = tmp_smoke_position
|
|
else
|
|
break
|
|
|
|
new /obj/effect/smoke(smoke_position, 2 SECONDS)
|
|
|
|
for(var/mob/living/carbon/human/H in range(3, src))
|
|
if(H.client)
|
|
shake_camera(H, 0.5 SECONDS, 2)
|
|
|
|
|
|
/*####################################
|
|
AMMUNITIONS ABSTRACT TYPES
|
|
####################################*/
|
|
|
|
/**
|
|
* # Howitzer Ammo Casing
|
|
*
|
|
* Howitzer ammo casing is a type of ammo casing that can be loaded into howitzers.
|
|
*/
|
|
ABSTRACT_TYPE(/obj/item/ammo_casing/howitzer)
|
|
name = "howitzer ammo casing"
|
|
icon = 'icons/obj/machinery/howitzer/howitzer_ammo.dmi'
|
|
icon_state = "howitzer_ammo"
|
|
spent_icon = "howitzer_ammo" //We don't have the sprite, just keep the default one
|
|
|
|
//We're reusing the PEAC sounds because I can't seem to find a better one
|
|
drop_sound = 'sound/items/drop/shell_drop.ogg'
|
|
pickup_sound = 'sound/items/pickup/weldingtool.ogg'
|
|
|
|
projectile_type = /obj/projectile/howitzer
|
|
|
|
/obj/item/ammo_casing/howitzer/feedback_hints(mob/user, distance, is_adjacent)
|
|
. += ..()
|
|
. += "\A [name], to be used in a howitzer."
|
|
if(!BB && distance < 4)
|
|
. += "This one is spent."
|
|
|
|
/**
|
|
* # Howitzer Ammo
|
|
*
|
|
* Howitzer ammo is a type of ammo that can be used in howitzers.
|
|
*/
|
|
ABSTRACT_TYPE(/obj/projectile/howitzer)
|
|
name = "howitzer ammo"
|
|
icon = 'icons/obj/machinery/howitzer/howitzer_ammo.dmi'
|
|
icon_state = "howitzer_ammo"
|
|
damage = 0
|
|
range = 999 //Follow what the path says, not range
|
|
|
|
/obj/projectile/howitzer/can_hit_target(atom/target, direct_target = FALSE, ignore_loc = FALSE, cross_failed = FALSE)
|
|
if(target == original)
|
|
return TRUE
|
|
else
|
|
return FALSE
|
|
|
|
//We have to handle collisions like the snowflake projectile we are. Or rewrite the projectile logic, you can do that if you want, I do not
|
|
/obj/projectile/howitzer/Collide(atom/A)
|
|
if(A == original)
|
|
on_hit(A)
|
|
qdel(src)
|
|
|
|
/obj/projectile/howitzer/on_hit(atom/target, blocked, def_zone)
|
|
. = ..()
|
|
|
|
if(target == original)
|
|
terminal_effect(get_turf(target))
|
|
|
|
/**
|
|
* Takes care of performing the terminal effect of the projectile
|
|
*
|
|
* * impacted_turf - The `/turf` that the projectile landed on
|
|
*/
|
|
/obj/projectile/howitzer/proc/terminal_effect(turf/impacted_turf)
|
|
SHOULD_NOT_SLEEP(TRUE)
|
|
SHOULD_CALL_PARENT(TRUE)
|
|
|
|
if(QDELETED(impacted_turf))
|
|
return FALSE
|
|
|
|
return TRUE
|
|
|
|
/*
|
|
High Explosive
|
|
*/
|
|
ABSTRACT_TYPE(/obj/item/ammo_casing/howitzer/high_explosive)
|
|
name = "high explosive howitzer ammo"
|
|
projectile_type = /obj/projectile/howitzer/high_explosive
|
|
|
|
/**
|
|
* # High Explosive howitzer ammo
|
|
*
|
|
* Big boom, many fragments
|
|
*/
|
|
ABSTRACT_TYPE(/obj/projectile/howitzer/high_explosive)
|
|
name = "high explosive howitzer ammo"
|
|
|
|
///The minimum number of fragments this HE shell will create
|
|
var/fragment_minimum = 80
|
|
///The maximum number of fragments this HE shell will create
|
|
var/fragment_maximum = 100
|
|
///The range in which the explosion will do light impact damage
|
|
var/light_impact_damage_range = 7
|
|
///The range in which the explosion will do flash damage
|
|
var/flash_damage_range = 7
|
|
///The damage the fragments will do, each
|
|
var/fragment_damage = 10
|
|
///The range in which the fragments will travel, the projectile will lose a pellet each time it travels this distance. Can be a non-integer.
|
|
var/fragment_lost_per_step = 7
|
|
///The range in which the fragments will travel
|
|
var/fragment_range = 7
|
|
///Boolean, if people can cover from the explosion
|
|
var/can_cover = TRUE
|
|
|
|
/obj/projectile/howitzer/high_explosive/terminal_effect(turf/impacted_turf)
|
|
. = ..()
|
|
if(!.)
|
|
return
|
|
|
|
impacted_turf.hotspot_expose(1 KILO, 100)
|
|
|
|
INVOKE_ASYNC(GLOBAL_PROC, GLOBAL_PROC_REF(fragem), impacted_turf, fragment_minimum, fragment_maximum, \
|
|
light_impact_damage_range, flash_damage_range, fragment_damage, \
|
|
fragment_lost_per_step, can_cover, fragment_range)
|
|
|
|
|
|
/*####################################
|
|
GUNPOWDER PELLETS
|
|
####################################*/
|
|
ABSTRACT_TYPE(/obj/item/howitzer_pellet)
|
|
name = "howitzer pellet"
|
|
desc = "A pellet that can be used in a howitzer to propel the projectile."
|
|
|
|
icon = 'icons/obj/machinery/howitzer/powder_pellets.dmi'
|
|
icon_state = "powder"
|
|
|
|
///How much power this pellet adds to the howitzer
|
|
var/power = 0
|
|
|
|
///How much precise is the value, in percentage, supposed to represent the powder quality
|
|
var/precision = 80
|
|
|
|
/obj/item/howitzer_pellet/Initialize(mapload, ...)
|
|
. = ..()
|
|
|
|
//Calculate the purity of the powder at initialization
|
|
var/lower_bound = power - (power * (100-precision) / 100)
|
|
var/upper_bound = power + (power * (100-precision) / 100)
|
|
power = rand(lower_bound, upper_bound)
|
|
|
|
/obj/item/howitzer_pellet/small
|
|
name = "small howitzer pellet"
|
|
|
|
power = 5
|
|
|
|
/obj/item/howitzer_pellet/medium
|
|
name = "medium howitzer pellet"
|
|
|
|
power = 15
|
|
|
|
/obj/item/howitzer_pellet/large
|
|
name = "large howitzer pellet"
|
|
icon_state = "hungrypowder"
|
|
|
|
power = 20
|
|
|
|
/*#####################################
|
|
ACTUAL HOWITZERS TO USE
|
|
#####################################*/
|
|
|
|
/obj/machinery/howitzer/gadpathur_105mm
|
|
name = "gadpathur 105mm light field howitzer"
|
|
desc = "A 105mm light field howitzer, in service with the Gadpathur Planetary Defense Council."
|
|
|
|
/obj/item/ammo_casing/howitzer/high_explosive/gadpathur_105mm
|
|
name = "gadpathur 105mm light field howitzer HE ammo"
|
|
desc = "A 105mm light field howitzer ammo, designed with the idea of obliterating any Solarian that might put foot on the surface of the planet."
|
|
projectile_type = /obj/projectile/howitzer/high_explosive/gadpathur_105mm
|
|
|
|
/obj/projectile/howitzer/high_explosive/gadpathur_105mm
|
|
name = "gadpathur 105mm light field howitzer HE projectile"
|
|
fragment_minimum = 30
|
|
fragment_maximum = 50
|
|
light_impact_damage_range = 3
|
|
flash_damage_range = 5
|