diff --git a/code/__DEFINES/_struct.dm b/code/__DEFINES/_struct.dm
new file mode 100644
index 000000000000..a6ffd2c83759
--- /dev/null
+++ b/code/__DEFINES/_struct.dm
@@ -0,0 +1,32 @@
+#define DEFSTRUCT(type) /datum/struct/##type; /datum/struct/##type/name = #type;
+
+#define DEFSTRUCTPROP(type, prop, _can_qdel) \
+/datum/struct/##type/var/##prop; \
+/datum/struct/##type/add_props() { \
+ ..(); \
+ can_qdel += _can_qdel; \
+ ##prop = STRUCTS.current_struct_idx++; \
+ props += #prop; \
+}
+
+#define ENDDEFSTRUCT(type) \
+/datum/struct/##type/proc/generate() { \
+ var/list/struct = new /list(src.len); \
+ struct[1] = src; \
+ return struct; \
+} \
+/datum/controller/structs/var/datum/struct/##type/##type; \
+/datum/controller/structs/proc/generate_struct_##type() { \
+ ##type = new(); \
+}
+
+#define STRUCT(type) STRUCTS.##type.generate();
+#define GETPROP(struct, prop) struct[struct[1].##prop]
+
+// Fun pseudo function: List.[1] works
+// However SpacemanDMM throws a fit about the syntax
+// #define PROP(type, prop) [STRUCTS.##type.##prop]
+
+#define ISSTRUCT(to_test, type) (length(to_test) ? to_test[1] == STRUCTS.##type : FALSE)
+
+#define STRUCT_PROP_STARTING_INDEX 2
diff --git a/code/__DEFINES/qdel.dm b/code/__DEFINES/qdel.dm
index 2093f80be50e..c25fa2c02b80 100644
--- a/code/__DEFINES/qdel.dm
+++ b/code/__DEFINES/qdel.dm
@@ -56,8 +56,40 @@
#define QDEL_IN(item, time) addtimer(CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(qdel), (time) > GC_FILTER_QUEUE ? WEAKREF(item) : item), time, TIMER_STOPPABLE)
#define QDEL_IN_CLIENT_TIME(item, time) addtimer(CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(qdel), item), time, TIMER_STOPPABLE | TIMER_CLIENT_TIME)
#define QDEL_NULL(item) qdel(item); item = null
-#define QDEL_NULL_LIST(x) if(x) { for(var/y in x) { qdel(y) }}; if(x) {x.Cut(); x = null } // Second x check to handle items that LAZYREMOVE on qdel.
+
+// Second x check to handle items that LAZYREMOVE on qdel.
+#define QDEL_NULL_LIST(x) \
+do { \
+ if(x) { for(var/y in x) { qdel(y) }}; \
+ if(x) {x.Cut(); x = null } \
+} while(FALSE);
+
+// Second struct check to handle items that LAZYREMOVE on qdel.
+#define QDEL_NULL_STRUCT(struct) \
+do { \
+ if(struct) { \
+ for(var/i in STRUCT_PROP_STARTING_INDEX to length(struct)) { \
+ if(struct[1].can_qdel[i] && !isnull(struct[i])) { \
+ qdel(struct[i]); \
+ } \
+ } \
+ }; \
+ if(struct) { \
+ struct.Cut(); \
+ struct = null; \
+ }; \
+} while(FALSE);
+
#define QDEL_LIST(L) if(L) { for(var/I in L) qdel(I); L.Cut(); }
#define QDEL_LIST_IN(L, time) addtimer(CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(______qdel_list_wrapper), L), time, TIMER_STOPPABLE)
#define QDEL_LIST_ASSOC(L) if(L) { for(var/I in L) { qdel(L[I]); qdel(I); } L.Cut(); }
#define QDEL_LIST_ASSOC_VAL(L) if(L) { for(var/I in L) qdel(L[I]); L.Cut(); }
+#define QDEL_STRUCT(struct) \
+if(struct) { \
+ for(var/i in STRUCT_PROP_STARTING_INDEX to length(struct)) { \
+ if(struct[1].can_qdel[i] && !isnull(struct[i])) { \
+ qdel(struct[i]); \
+ } \
+ }; \
+ struct.Cut(); \
+}
diff --git a/code/_globalvars/struct.dm b/code/_globalvars/struct.dm
new file mode 100644
index 000000000000..b79e7b7ca75f
--- /dev/null
+++ b/code/_globalvars/struct.dm
@@ -0,0 +1 @@
+GLOBAL_REAL(STRUCTS, /datum/controller/structs)
diff --git a/code/controllers/mc/master.dm b/code/controllers/mc/master.dm
index 740e29949418..322398e91ab1 100644
--- a/code/controllers/mc/master.dm
+++ b/code/controllers/mc/master.dm
@@ -123,6 +123,8 @@ GLOBAL_REAL(Master, /datum/controller/master) = new
if(!GLOB)
new /datum/controller/global_vars
+ if(!STRUCTS)
+ new /datum/controller/structs
/datum/controller/master/Destroy()
..()
diff --git a/code/controllers/struct.dm b/code/controllers/struct.dm
new file mode 100644
index 000000000000..d16d79ed5ec9
--- /dev/null
+++ b/code/controllers/struct.dm
@@ -0,0 +1,43 @@
+/datum/controller/structs
+ /**
+ * Value for calculating the index of the struct currently being
+ * generated
+ * Should always be above 1, as index 1 is reserved for the struct name
+ */
+ var/static/current_struct_idx = STRUCT_PROP_STARTING_INDEX
+
+ var/list/structs_datum_in_built_vars
+
+/datum/controller/structs/New()
+ if(STRUCTS)
+ CRASH("Multiple instances of structs controller created")
+ STRUCTS = src
+
+ var/datum/controller/exclude_these = new
+ // I know this is dumb but the nested vars list hangs a ref to the datum. This fixes that
+ var/list/controller_vars = exclude_these.vars.Copy()
+ controller_vars["vars"] = null
+ structs_datum_in_built_vars = controller_vars + list(NAMEOF(src, current_struct_idx))
+
+ QDEL_IN(exclude_these, 0) //signal logging isn't ready
+
+ log_world("[length(vars) - length(structs_datum_in_built_vars)] structs")
+
+ Initialize()
+
+/datum/controller/structs/Initialize()
+ var/list/struct_initializers = typesof(/datum/controller/structs/proc)
+ var/expected_len = length(vars) - length(structs_datum_in_built_vars)
+ if(length(struct_initializers) != expected_len)
+ warning("Unable to detect all struct initialization procs! Expected [expected_len] got [length(struct_initializers)]!")
+ if(length(struct_initializers))
+ var/list/expected_struct_initializers = vars - structs_datum_in_built_vars
+ for(var/initializer in struct_initializers)
+ expected_struct_initializers -= replacetext("[initializer]", "generate_struct_", "")
+ log_world("Structs with missing initializers: [expected_struct_initializers.Join(", ")]")
+ for(var/initializer in struct_initializers)
+ var/start_tick = world.time
+ call(src, initializer)()
+ var/end_tick = world.time
+ if(end_tick - start_tick)
+ warning("Struct [replacetext("[initializer]", "generate_struct_", "")] slept during initialization!")
diff --git a/code/datums/structs/_struct.dm b/code/datums/structs/_struct.dm
new file mode 100644
index 000000000000..6acf7e3b43eb
--- /dev/null
+++ b/code/datums/structs/_struct.dm
@@ -0,0 +1,25 @@
+/datum/struct
+ var/name = "UNDEFINED"
+ var/list/props
+ /// The length of the lists to be generated
+ /// which represent a struct
+ var/len
+
+ /// Standard list mapped 1:1 to the list produced when
+ /// struct is created using STRUCT(...)
+ var/list/can_qdel
+
+/datum/struct/New()
+ props = list()
+ // First element of struct list is a string containing the struct name
+ can_qdel = list(FALSE)
+ STRUCTS.current_struct_idx = STRUCT_PROP_STARTING_INDEX
+ add_props()
+ // Index will go up by 1 when adding last element
+ len = STRUCTS.current_struct_idx - 1
+
+/datum/struct/proc/add_props()
+ return
+
+/datum/struct/proc/operator""()
+ return "struct [name]([props.Join(", ")])"
diff --git a/code/datums/structs/launch_metadata.dm b/code/datums/structs/launch_metadata.dm
new file mode 100644
index 000000000000..d7867e414bb9
--- /dev/null
+++ b/code/datums/structs/launch_metadata.dm
@@ -0,0 +1,12 @@
+DEFSTRUCT(launch_metadata)
+DEFSTRUCTPROP(launch_metadata, pass_flags, FALSE)
+DEFSTRUCTPROP(launch_metadata, call_all, FALSE)
+DEFSTRUCTPROP(launch_metadata, target, FALSE)
+DEFSTRUCTPROP(launch_metadata, range, FALSE)
+DEFSTRUCTPROP(launch_metadata, speed, FALSE)
+DEFSTRUCTPROP(launch_metadata, thrower, FALSE)
+DEFSTRUCTPROP(launch_metadata, spin, FALSE)
+DEFSTRUCTPROP(launch_metadata, collision_callbacks, FALSE)
+DEFSTRUCTPROP(launch_metadata, end_throw_callbacks, FALSE)
+DEFSTRUCTPROP(launch_metadata, dist, FALSE)
+ENDDEFSTRUCT(launch_metadata)
diff --git a/code/game/atoms_movable.dm b/code/game/atoms_movable.dm
index be68dddff10f..5d6fb3206776 100644
--- a/code/game/atoms_movable.dm
+++ b/code/game/atoms_movable.dm
@@ -40,7 +40,7 @@
qdel(I)
if(pulledby)
pulledby.stop_pulling()
- QDEL_NULL(launch_metadata)
+ QDEL_STRUCT(launch_metadata)
QDEL_NULL(em_block)
QDEL_NULL(emissive_overlay)
diff --git a/code/game/objects/items/explosives/grenades/grenade.dm b/code/game/objects/items/explosives/grenades/grenade.dm
index b2f95646a966..542f4658210c 100644
--- a/code/game/objects/items/explosives/grenades/grenade.dm
+++ b/code/game/objects/items/explosives/grenades/grenade.dm
@@ -121,10 +121,11 @@
dangerous = FALSE
. = ..()
-/obj/item/explosive/grenade/launch_towards(datum/launch_metadata/LM)
- if(active && ismob(LM.thrower))
- var/mob/M = LM.thrower
- M.count_niche_stat(STATISTICS_NICHE_GRENADES)
+/obj/item/explosive/grenade/launch_towards(list/launch_metadata)
+ var/thrower = GETPROP(launch_metadata, thrower)
+ if(active && ismob(thrower))
+ var/mob/mob_thrower = thrower
+ mob_thrower.count_niche_stat(STATISTICS_NICHE_GRENADES)
. = ..()
diff --git a/code/game/objects/items/shards.dm b/code/game/objects/items/shards.dm
index dab573e6f5a5..1e44d63fa6c2 100644
--- a/code/game/objects/items/shards.dm
+++ b/code/game/objects/items/shards.dm
@@ -133,8 +133,7 @@
/obj/item/large_shrapnel/at_rocket_dud/launch_impact(atom/hit_atom)
. = ..()
- var/datum/launch_metadata/LM = src.launch_metadata
- var/user = LM.thrower
+ var/user = GETPROP(launch_metadata, thrower)
if(!detonating && prob(impact_sensitivity))
cause = "manually triggered"
visible_message(SPAN_DANGER("You hear the click of a mechanism triggering inside \the [src]. Uh oh."))
diff --git a/code/game/objects/structures/window.dm b/code/game/objects/structures/window.dm
index 14e15de24691..bb28b6a41d8f 100644
--- a/code/game/objects/structures/window.dm
+++ b/code/game/objects/structures/window.dm
@@ -179,7 +179,8 @@
anchored = FALSE
update_nearby_icons()
step(src, get_dir(AM, src))
- healthcheck(user = AM.launch_metadata.thrower)
+ var/source = GETPROP(AM.launch_metadata, thrower)
+ healthcheck(user = source)
/obj/structure/window/attack_hand(mob/user as mob)
if(user.a_intent == INTENT_HARM && ishuman(user))
diff --git a/code/game/turfs/open.dm b/code/game/turfs/open.dm
index 680348e4d995..1c3cff421100 100644
--- a/code/game/turfs/open.dm
+++ b/code/game/turfs/open.dm
@@ -779,15 +779,15 @@
return
var/mob/unlucky_mob = AM
var/turf/target_turf = get_random_turf_in_range(AM.loc, 3, 0)
- var/datum/launch_metadata/LM = new()
- LM.target = target_turf
- LM.range = get_dist(AM.loc, target_turf)
- LM.speed = SPEED_FAST
- LM.thrower = unlucky_mob
- LM.spin = TRUE
- LM.pass_flags = NO_FLAGS
+ var/list/launch_metadata = STRUCT(launch_metadata)
+ GETPROP(launch_metadata, target) = target_turf
+ GETPROP(launch_metadata, range) = get_dist(AM.loc, target_turf)
+ GETPROP(launch_metadata, speed) = SPEED_FAST
+ GETPROP(launch_metadata, thrower) = unlucky_mob
+ GETPROP(launch_metadata, spin) = TRUE
+ GETPROP(launch_metadata, pass_flags) = NO_FLAGS
to_chat(unlucky_mob, SPAN_WARNING("The ocean currents sweep you off your feet and throw you away!"))
- unlucky_mob.launch_towards(LM)
+ unlucky_mob.launch_towards(launch_metadata)
return
if(world.time % 5)
diff --git a/code/modules/cm_preds/smartdisc.dm b/code/modules/cm_preds/smartdisc.dm
index f0262f5f140a..de6a4b706f46 100644
--- a/code/modules/cm_preds/smartdisc.dm
+++ b/code/modules/cm_preds/smartdisc.dm
@@ -20,7 +20,7 @@
force = 15
throwforce = 25
-/obj/item/explosive/grenade/spawnergrenade/smartdisc/launch_towards(datum/launch_metadata/LM)
+/obj/item/explosive/grenade/spawnergrenade/smartdisc/launch_towards(list/launch_metadata)
..()
var/mob/user = usr
if(!active && isyautja(user) && (icon_state == initial(icon_state)))
diff --git a/code/modules/mob/living/carbon/human/human_defense.dm b/code/modules/mob/living/carbon/human/human_defense.dm
index 09ce5bb9c149..c9092fb8b8ff 100644
--- a/code/modules/mob/living/carbon/human/human_defense.dm
+++ b/code/modules/mob/living/carbon/human/human_defense.dm
@@ -265,96 +265,98 @@ Contains most of the procs that are called when a mob is attacked by something
return TRUE
//this proc handles being hit by a thrown atom
-/mob/living/carbon/human/hitby(atom/movable/AM)
- if (!isobj(AM))
+/mob/living/carbon/human/hitby(atom/movable/thrown_atom)
+ if (!isobj(thrown_atom))
return
- var/obj/O = AM
- var/datum/launch_metadata/LM = O.launch_metadata
+ var/obj/thrown_object = thrown_atom
+ var/list/launch_metadata = thrown_object.launch_metadata
//empty active hand and we're in throw mode
- var/can_catch = (!(O.flags_atom & ITEM_UNCATCHABLE) || isyautja(src))
+ var/can_catch = (!(thrown_object.flags_atom & ITEM_UNCATCHABLE) || isyautja(src))
if (throw_mode && can_catch && !get_active_hand() && cur_speed <= SPEED_VERY_FAST && \
- !is_mob_incapacitated() && isturf(O.loc) && put_in_active_hand(O)
+ !is_mob_incapacitated() && isturf(thrown_object.loc) && put_in_active_hand(thrown_object)
)
- visible_message(SPAN_WARNING("[src] catches [O]!"), null, null, 5)
+ visible_message(SPAN_WARNING("[src] catches [thrown_object]!"), null, null, 5)
toggle_throw_mode(THROW_MODE_OFF)
return
var/dtype = BRUTE
- if (istype(O, /obj/item/weapon))
- var/obj/item/weapon/W = O
- dtype = W.damtype
- var/impact_damage = (1 + O.throwforce*THROWFORCE_COEFF)*O.throwforce*THROW_SPEED_IMPACT_COEFF*O.cur_speed
+ if (istype(thrown_object, /obj/item/weapon))
+ var/obj/item/weapon/thrown_weapon = thrown_object
+ dtype = thrown_weapon.damtype
+ var/impact_damage = (1 + thrown_object.throwforce*THROWFORCE_COEFF)*thrown_object.throwforce*THROW_SPEED_IMPACT_COEFF*thrown_object.cur_speed
var/zone
- if (istype(LM.thrower, /mob/living))
- var/mob/living/L = LM.thrower
- zone = check_zone(L.zone_selected)
+ var/thrower = GETPROP(launch_metadata, thrower)
+ if (istype(thrower, /mob/living))
+ var/mob/living/living_thrower = thrower
+ zone = check_zone(living_thrower.zone_selected)
else
zone = rand_zone("chest", 75) //Hits a random part of the body, geared towards the chest
if (!zone)
- visible_message(SPAN_NOTICE("\The [O] misses [src] narrowly!"), null, null, 5)
+ visible_message(SPAN_NOTICE("\The [thrown_object] misses [src] narrowly!"), null, null, 5)
return
- O.throwing = FALSE //it hit, so stop moving
+ thrown_object.throwing = FALSE //it hit, so stop moving
- if ((LM.thrower != src) && check_shields(impact_damage, "[O]"))
+ if ((thrower != src) && check_shields(impact_damage, "[thrown_object]"))
return
var/obj/limb/affecting = get_limb(zone)
var/hit_area = affecting.display_name
- src.visible_message(SPAN_DANGER("[src] has been hit in the [hit_area] by [O]."), null, null, 5)
+ src.visible_message(SPAN_DANGER("[src] has been hit in the [hit_area] by [thrown_object]."), null, null, 5)
var/armor = getarmor(affecting, ARMOR_MELEE)
- var/weapon_sharp = is_sharp(O)
- var/weapon_edge = has_edge(O)
+ var/weapon_sharp = is_sharp(thrown_object)
+ var/weapon_edge = has_edge(thrown_object)
var/damage = armor_damage_reduction(GLOB.marine_melee, impact_damage, armor, (weapon_sharp?30:0) + (weapon_edge?10:0))
- apply_damage(damage, dtype, affecting, sharp=weapon_sharp, edge=weapon_edge, used_weapon=O)
+ apply_damage(damage, dtype, affecting, sharp=weapon_sharp, edge=weapon_edge, used_weapon=thrown_object)
var/last_damage_source = null
var/last_damage_mob = null
if (damage > 5)
- last_damage_source = initial(AM.name)
+ last_damage_source = initial(thrown_atom.name)
animation_flash_color(src)
- var/obj/item/I = O
- if(istype(I) && I.sharp) //Hilarious is_sharp only returns true if it's sharp AND edged, while a bunch of things don't have edge to limit embeds.
+ var/obj/item/thrown_item = thrown_object
+ if(istype(thrown_item) && thrown_item.sharp) //Hilarious is_sharp only returns true if it's sharp AND edged, while a bunch of things don't have edge to limit embeds.
playsound(loc, 'sound/effects/spike_hit.ogg', 20, TRUE, 5, falloff = 2)
else
playsound(loc, 'sound/effects/thud.ogg', 25, TRUE, 5, falloff = 2)
- if (ismob(LM.thrower))
- var/mob/M = LM.thrower
- var/client/assailant = M.client
+ if (ismob(thrower))
+ var/mob/mob_thrower = thrower
+ var/client/assailant = mob_thrower.client
if (damage > 5)
- last_damage_mob = M
- M.track_hit(initial(O.name))
- if (M.faction == faction)
- M.track_friendly_fire(initial(O.name))
+ last_damage_mob = mob_thrower
+ mob_thrower.track_hit(initial(thrown_object.name))
+ if (mob_thrower.faction == faction)
+ mob_thrower.track_friendly_fire(initial(thrown_object.name))
if (assailant)
- src.attack_log += text("\[[time_stamp()]\] Has been hit with \a [O], thrown by [key_name(M)]")
- M.attack_log += text("\[[time_stamp()]\] Hit [key_name(src)] with a thrown [O]")
+ src.attack_log += text("\[[time_stamp()]\] Has been hit with \a [thrown_object], thrown by [key_name(mob_thrower)]")
+ mob_thrower.attack_log += text("\[[time_stamp()]\] Hit [key_name(src)] with a thrown [thrown_object]")
if(!istype(src,/mob/living/simple_animal/mouse))
- msg_admin_attack("[key_name(src)] was hit by \a [O], thrown by [key_name(M)] in [get_area(src)] ([src.loc.x],[src.loc.y],[src.loc.z]).", src.loc.x, src.loc.y, src.loc.z)
+ msg_admin_attack("[key_name(src)] was hit by \a [thrown_object], thrown by [key_name(mob_thrower)] in [get_area(src)] ([src.loc.x],[src.loc.y],[src.loc.z]).", src.loc.x, src.loc.y, src.loc.z)
- if(last_damage_source)
+ if (last_damage_source)
last_damage_data = create_cause_data(last_damage_source, last_damage_mob)
//thrown weapon embedded object code.
- if (dtype == BRUTE && istype(O,/obj/item))
- var/obj/item/I = O
- var/sharp = is_sharp(I)
+ if (dtype == BRUTE && istype(thrown_object, /obj/item))
+ var/obj/item/thrown_item = thrown_object
+ var/sharp = is_sharp(thrown_item)
+ var/weight_class = thrown_item.w_class
//blunt objects should really not be embedding in things unless a huge amount of force is involved
- var/embed_chance = sharp? damage/I.w_class : damage/(I.w_class*3)
- var/embed_threshold = sharp? 5*I.w_class : 15*I.w_class
+ var/embed_chance = sharp ? damage/weight_class : damage/(weight_class*3)
+ var/embed_threshold = sharp ? 5*weight_class : 15*weight_class
//Sharp objects will always embed if they do enough damage.
//Thrown sharp objects have some momentum already and have a small chance to embed even if the damage is below the threshold
- if (!isyautja(src) && ((sharp && prob(damage/(10*I.w_class)*100)) || (damage > embed_threshold && prob(embed_chance))))
- affecting.embed(I)
+ if (!isyautja(src) && ((sharp && prob(damage/(10*weight_class)*100)) || (damage > embed_threshold && prob(embed_chance))))
+ affecting.embed(thrown_item)
/mob/living/carbon/human/proc/get_id_faction_group()
var/obj/item/card/id/C = wear_id
diff --git a/code/modules/mob/living/carbon/xenomorph/Facehuggers.dm b/code/modules/mob/living/carbon/xenomorph/Facehuggers.dm
index 9a87f10d74a3..c2b8e4912b28 100644
--- a/code/modules/mob/living/carbon/xenomorph/Facehuggers.dm
+++ b/code/modules/mob/living/carbon/xenomorph/Facehuggers.dm
@@ -209,7 +209,7 @@
return TRUE
return FALSE
-/obj/item/clothing/mask/facehugger/launch_towards(datum/launch_metadata/LM)
+/obj/item/clothing/mask/facehugger/launch_towards(list/launch_metadata)
if(stat == CONSCIOUS)
icon_state = "[initial(icon_state)]_thrown"
..()
diff --git a/code/modules/mob/living/living.dm b/code/modules/mob/living/living.dm
index 27697a8938a7..748edc0d7497 100644
--- a/code/modules/mob/living/living.dm
+++ b/code/modules/mob/living/living.dm
@@ -410,13 +410,17 @@
..()
-/mob/living/launch_towards(datum/launch_metadata/LM)
+/mob/living/launch_towards(list/launch_metadata)
if(src)
SEND_SIGNAL(src, COMSIG_MOB_MOVE_OR_LOOK, TRUE, dir, dir)
- if(!istype(LM) || !LM.target || !src)
+ if(!ISSTRUCT(launch_metadata, launch_metadata) || !src)
return
+ var/target = GETPROP(launch_metadata, target)
+ if(!target)
+ return
+
if(buckled)
- LM.invoke_end_throw_callbacks(src)
+ invoke_end_throw_callbacks(launch_metadata, src)
return
if(pulling)
stop_pulling() //being thrown breaks pulls.
diff --git a/code/modules/mob/living/living_defense.dm b/code/modules/mob/living/living_defense.dm
index b19afb0450cd..234632ae4b52 100644
--- a/code/modules/mob/living/living_defense.dm
+++ b/code/modules/mob/living/living_defense.dm
@@ -39,10 +39,12 @@
dtype = W.damtype
var/impact_damage = (1 + O.throwforce*THROWFORCE_COEFF)*O.throwforce*THROW_SPEED_IMPACT_COEFF*O.cur_speed
- var/datum/launch_metadata/LM = O.launch_metadata
+ var/list/launch_metadata = O.launch_metadata
var/dist = 2
- if(istype(LM))
- dist = LM.dist
+ var/thrower
+ if(ISSTRUCT(launch_metadata, launch_metadata))
+ dist = GETPROP(launch_metadata, dist)
+ thrower = GETPROP(launch_metadata, thrower)
var/miss_chance = min(15*(dist - 2), 0)
if (prob(miss_chance))
@@ -64,31 +66,31 @@
O.throwing = 0 //it hit, so stop moving
- var/mob/M
- if(ismob(LM.thrower))
- M = LM.thrower
+ var/mob/mob_thrower
+ if(ismob(thrower))
+ mob_thrower = thrower
if(damage_done > 5)
- M.track_hit(initial(O.name))
- if (M.faction == faction)
- M.track_friendly_fire(initial(O.name))
- var/client/assailant = M.client
+ mob_thrower.track_hit(initial(O.name))
+ if (mob_thrower.faction == faction)
+ mob_thrower.track_friendly_fire(initial(O.name))
+ var/client/assailant = mob_thrower.client
if(assailant)
- src.attack_log += text("\[[time_stamp()]\] Has been hit with \a [O], thrown by [key_name(M)]")
- M.attack_log += text("\[[time_stamp()]\] Hit [key_name(src)] with a thrown [O]")
+ src.attack_log += text("\[[time_stamp()]\] Has been hit with \a [O], thrown by [key_name(mob_thrower)]")
+ mob_thrower.attack_log += text("\[[time_stamp()]\] Hit [key_name(src)] with a thrown [O]")
if(!istype(src,/mob/living/simple_animal/mouse))
if(src.loc)
- msg_admin_attack("[key_name(src)] was hit by \a [O], thrown by [key_name(M)] in [get_area(src)] ([src.loc.x],[src.loc.y],[src.loc.z]).", src.loc.x, src.loc.y, src.loc.z)
+ msg_admin_attack("[key_name(src)] was hit by \a [O], thrown by [key_name(mob_thrower)] in [get_area(src)] ([src.loc.x],[src.loc.y],[src.loc.z]).", src.loc.x, src.loc.y, src.loc.z)
else
- msg_admin_attack("[key_name(src)] was hit by \a [O], thrown by [key_name(M)] in [get_area(M)] ([M.loc.x],[M.loc.y],[M.loc.z]).", M.loc.x, M.loc.y, M.loc.z)
+ msg_admin_attack("[key_name(src)] was hit by \a [O], thrown by [key_name(mob_thrower)] in [get_area(mob_thrower)] ([mob_thrower.loc.x],[mob_thrower.loc.y],[mob_thrower.loc.z]).", mob_thrower.loc.x, mob_thrower.loc.y, mob_thrower.loc.z)
if(last_damage_source)
- last_damage_data = create_cause_data(last_damage_source, M)
+ last_damage_data = create_cause_data(last_damage_source, mob_thrower)
/mob/living/mob_launch_collision(mob/living/L)
L.Move(get_step_away(L, src))
/mob/living/obj_launch_collision(obj/O)
- var/datum/launch_metadata/LM = launch_metadata
- if(!rebounding && LM.thrower != src)
+ var/atom/thrower = GETPROP(launch_metadata, thrower)
+ if(!rebounding && thrower != src)
var/impact_damage = (1 + MOB_SIZE_COEFF/(mob_size + 1))*THROW_SPEED_DENSE_COEFF*cur_speed
apply_damage(impact_damage)
visible_message(SPAN_DANGER("\The [name] slams into [O]!"), null, null, 5) //feedback to know that you got slammed into a wall and it hurt
@@ -97,10 +99,10 @@
//This is called when the mob or human is thrown into a dense turf or wall
/mob/living/turf_launch_collision(turf/T)
- var/datum/launch_metadata/LM = launch_metadata
- if(!rebounding && LM.thrower != src)
- if(LM.thrower)
- last_damage_data = create_cause_data("wall tossing", LM.thrower)
+ var/atom/thrower = GETPROP(launch_metadata, thrower)
+ if(!rebounding && thrower != src)
+ if(thrower)
+ last_damage_data = create_cause_data("wall tossing", thrower)
var/impact_damage = (1 + MOB_SIZE_COEFF/(mob_size + 1))*THROW_SPEED_DENSE_COEFF*cur_speed
apply_damage(impact_damage)
visible_message(SPAN_DANGER("\The [name] slams into [T]!"), null, null, 5) //feedback to know that you got slammed into a wall and it hurt
diff --git a/code/modules/movement/launching/launching.dm b/code/modules/movement/launching/launching.dm
index 778c452a3240..2e01c253d0fc 100644
--- a/code/modules/movement/launching/launching.dm
+++ b/code/modules/movement/launching/launching.dm
@@ -1,36 +1,8 @@
-/datum/launch_metadata
- // Parameters
- var/pass_flags = NO_FLAGS // Pass flags to add temporarily
- var/call_all = FALSE // Whether to perform all callback calls or none of them
-
- var/atom/target
- var/range
- var/speed
- var/atom/thrower
- var/spin
-
- // A list of callbacks to invoke when an atom of a specific type is hit (keys are typepaths and values are proc paths)
- // These should only be for CUSTOM procs to invoke when an atom of a specific type is collided with, otherwise will default to using
- // the appropriate mob/obj/turf collision procs
- // The callbacks can be standard or dynamic, though dynamic callbacks can only be called by the atom being thrown
- var/list/collision_callbacks
-
- /// A list of callbacks to invoke when the throw completes successfully
- var/list/end_throw_callbacks
-
- // Tracked information
- var/dist = 0
-
-/datum/launch_metadata/Destroy(force, ...)
- target = null
- thrower = null
- return ..()
-
-
-/datum/launch_metadata/proc/get_collision_callbacks(atom/A)
+/proc/get_collision_callbacks(list/launch_metadata, atom/A)
var/highest_matching = null
var/list/matching = list()
+ var/collision_callbacks = GETPROP(launch_metadata, collision_callbacks)
if (isnull(collision_callbacks))
return null
@@ -42,6 +14,7 @@
highest_matching = path
matching += path
+ var/call_all = GETPROP(launch_metadata, call_all)
if (!call_all)
if (isnull(highest_matching))
return null
@@ -56,7 +29,8 @@
/// Invoke end_throw_callbacks on this metadata.
/// Takes argument of type /atom/movable
-/datum/launch_metadata/proc/invoke_end_throw_callbacks(atom/movable/movable_atom)
+/proc/invoke_end_throw_callbacks(list/launch_metadata, atom/movable/movable_atom)
+ var/end_throw_callbacks = GETPROP(launch_metadata, end_throw_callbacks)
if(length(end_throw_callbacks))
for(var/datum/callback/callback as anything in end_throw_callbacks)
if(istype(callback, /datum/callback/dynamic))
@@ -64,14 +38,16 @@
else
callback.Invoke()
-/atom/movable/var/datum/launch_metadata/launch_metadata = null
+/// Struct containing information to process when a mob is thrown
+/// and how impacting mobs react to it
+/atom/movable/var/list/launch_metadata
//called when src is thrown into hit_atom
/atom/movable/proc/launch_impact(atom/hit_atom)
if (isnull(launch_metadata))
CRASH("launch_impact called without any stored metadata")
- var/list/collision_callbacks = launch_metadata.get_collision_callbacks(hit_atom)
+ var/list/collision_callbacks = get_collision_callbacks(launch_metadata, hit_atom)
if (islist(collision_callbacks))
for(var/datum/callback/CB as anything in collision_callbacks)
if(istype(CB, /datum/callback/dynamic))
@@ -117,14 +93,13 @@
/atom/movable/proc/rebound(oldloc, launched_speed)
if (loc == oldloc)
rebounding = TRUE
- var/datum/launch_metadata/LM = new()
- LM.target = get_step(src, turn(dir, 180))
- LM.range = 1
- LM.speed = launched_speed
- LM.pass_flags = PASS_UNDER
- LM.pass_flags |= (ismob(src) ? PASS_OVER_THROW_MOB : PASS_OVER_THROW_ITEM)
+ var/list/launch_metadata = STRUCT(launch_metadata)
+ GETPROP(launch_metadata, target) = get_step(src, turn(dir, 180))
+ GETPROP(launch_metadata, range) = 1
+ GETPROP(launch_metadata, speed) = launched_speed
+ GETPROP(launch_metadata, pass_flags) = PASS_UNDER|(ismob(src) ? PASS_OVER_THROW_MOB : PASS_OVER_THROW_ITEM)
- launch_towards(LM)
+ launch_towards(launch_metadata)
/atom/movable/proc/try_to_throw(mob/living/user)
return TRUE
@@ -138,68 +113,74 @@
if (HIGH_LAUNCH)
temp_pass_flags |= PASS_HIGH_OVER
- var/datum/launch_metadata/LM = new()
- LM.pass_flags = temp_pass_flags
- LM.target = target
- LM.range = range
- LM.speed = speed
- LM.thrower = thrower
- LM.spin = spin
+ var/list/launch_metadata = STRUCT(launch_metadata)
+ GETPROP(launch_metadata, pass_flags) = temp_pass_flags
+ GETPROP(launch_metadata, target) = target
+ GETPROP(launch_metadata, range) = range
+ GETPROP(launch_metadata, speed) = speed
+ GETPROP(launch_metadata, thrower) = thrower
+ GETPROP(launch_metadata, spin) = spin
if(end_throw_callbacks)
- LM.end_throw_callbacks = end_throw_callbacks
+ GETPROP(launch_metadata, end_throw_callbacks) = end_throw_callbacks
if(collision_callbacks)
- LM.collision_callbacks = collision_callbacks
+ GETPROP(launch_metadata, collision_callbacks) = collision_callbacks
- if(SEND_SIGNAL(src, COMSIG_MOVABLE_PRE_LAUNCH, LM) & COMPONENT_LAUNCH_CANCEL)
+ if(SEND_SIGNAL(src, COMSIG_MOVABLE_PRE_LAUNCH, launch_metadata) & COMPONENT_LAUNCH_CANCEL)
return
- launch_towards(LM)
+ launch_towards(launch_metadata)
// Proc for throwing or propelling movable atoms towards a target
-/atom/movable/proc/launch_towards(datum/launch_metadata/LM)
- if (!istype(LM))
+/atom/movable/proc/launch_towards(list/new_launch_metadata)
+ if (!ISSTRUCT(new_launch_metadata, launch_metadata))
CRASH("invalid launch_metadata passed to launch_towards")
- if (!LM.target || !src)
+
+ var/target = GETPROP(new_launch_metadata, target)
+ if (!target || !src)
return
- if(SEND_SIGNAL(src, COMSIG_MOVABLE_PRE_THROW, LM.thrower) & COMPONENT_CANCEL_THROW)
+ var/thrower = GETPROP(new_launch_metadata, thrower)
+ if(SEND_SIGNAL(src, COMSIG_MOVABLE_PRE_THROW, thrower) & COMPONENT_CANCEL_THROW)
return
- // If we already have launch_metadata (from a previous throw), reset it and qdel the old launch_metadata datum
- if (istype(launch_metadata))
- qdel(launch_metadata)
- launch_metadata = LM
+ launch_metadata = new_launch_metadata
- if (LM.spin)
- animation_spin(5, 1 + min(1, LM.range/20))
+ var/spin = GETPROP(new_launch_metadata, spin)
+ if (spin)
+ var/range = GETPROP(new_launch_metadata, spin)
+ animation_spin(5, 1 + min(1, range/20))
var/old_speed = cur_speed
- cur_speed = clamp(LM.speed, MIN_SPEED, MAX_SPEED) // Sanity check, also ~1 sec delay between each launch move is not very reasonable
+ var/metadata_speed = GETPROP(new_launch_metadata, speed)
+ cur_speed = clamp(metadata_speed, MIN_SPEED, MAX_SPEED) // Sanity check, also ~1 sec delay between each launch move is not very reasonable
var/delay = 10/cur_speed - 0.5 // scales delay back to deciseconds for when sleep is called
- var/pass_flags = LM.pass_flags
+ var/pass_flags = GETPROP(new_launch_metadata, pass_flags)
throwing = TRUE
add_temp_pass_flags(pass_flags)
- var/turf/start_turf = get_step_towards(src, LM.target)
- var/list/turf/path = get_line(start_turf, LM.target)
+ var/turf/start_turf = get_step_towards(src, target)
+ var/list/turf/path = get_line(start_turf, target)
var/last_loc = loc
var/early_exit = FALSE
- LM.dist = 0
+ GETPROP(launch_metadata, dist) = 0
for (var/turf/T in path)
if (!src || !throwing || loc != last_loc || !isturf(src.loc))
break
- if (!LM || QDELETED(LM))
+ if (!ISSTRUCT(launch_metadata, launch_metadata))
early_exit = TRUE
break
- if (LM.dist >= LM.range)
+ var/dist = GETPROP(launch_metadata, dist)
+ var/range = GETPROP(launch_metadata, range)
+ if (dist >= range)
break
if (!Move(T)) // If this returns FALSE, then a collision happened
break
last_loc = loc
- if (++LM.dist >= LM.range)
+ dist = ++GETPROP(launch_metadata, dist)
+ if (dist >= range)
break
sleep(delay)
@@ -207,11 +188,12 @@
if ((isobj(src) || ismob(src)) && throwing && !early_exit)
var/turf/T = get_turf(src)
if(!istype(T))
+ QDEL_NULL_STRUCT(new_launch_metadata)
return
- var/atom/hit_atom = ismob(LM.target) ? null : T // TODO, just check for LM.target, the ismob is to prevent funky behavior with grenades 'n crates
+ var/atom/hit_atom = ismob(target) ? null : T // TODO, just check for LM.target, the ismob is to prevent funky behavior with grenades 'n crates
if(!hit_atom)
for(var/atom/A in T)
- if(A == LM.target)
+ if(A == target)
hit_atom = A
break
launch_impact(hit_atom)
@@ -220,8 +202,8 @@
rebounding = FALSE
cur_speed = old_speed
remove_temp_pass_flags(pass_flags)
- LM.invoke_end_throw_callbacks(src)
- QDEL_NULL(launch_metadata)
+ invoke_end_throw_callbacks(new_launch_metadata, src)
+ QDEL_NULL_STRUCT(new_launch_metadata)
/atom/movable/proc/throw_random_direction(range, speed = 0, atom/thrower, spin, launch_type = NORMAL_LAUNCH, pass_flags = NO_FLAGS)
var/throw_direction = pick(CARDINAL_ALL_DIRS)
diff --git a/colonialmarines.dme b/colonialmarines.dme
index 48996a93ec30..73b43309e6e7 100644
--- a/colonialmarines.dme
+++ b/colonialmarines.dme
@@ -28,6 +28,7 @@
#include "code\__DEFINES\_macros.dm"
#include "code\__DEFINES\_math.dm"
#include "code\__DEFINES\_protect.dm"
+#include "code\__DEFINES\_struct.dm"
#include "code\__DEFINES\_tick.dm"
#include "code\__DEFINES\access.dm"
#include "code\__DEFINES\admin.dm"
@@ -202,6 +203,7 @@
#include "code\_globalvars\global_lists.dm"
#include "code\_globalvars\misc.dm"
#include "code\_globalvars\regexes.dm"
+#include "code\_globalvars\struct.dm"
#include "code\_globalvars\tgui.dm"
#include "code\_globalvars\lists\clans.dm"
#include "code\_globalvars\lists\client.dm"
@@ -239,6 +241,7 @@
#include "code\_onclick\hud\rendering\plane_master_controller.dm"
#include "code\_onclick\hud\rendering\render_plate.dm"
#include "code\controllers\shuttle_controller.dm"
+#include "code\controllers\struct.dm"
#include "code\controllers\topic.dm"
#include "code\controllers\configuration\config_entry.dm"
#include "code\controllers\configuration\configuration.dm"
@@ -672,6 +675,8 @@
#include "code\datums\status_effects\limited_effect.dm"
#include "code\datums\status_effects\stacking_effect.dm"
#include "code\datums\status_effects\debuffs\debuffs.dm"
+#include "code\datums\structs\_struct.dm"
+#include "code\datums\structs\launch_metadata.dm"
#include "code\datums\supply_packs\_supply_packs.dm"
#include "code\datums\supply_packs\ammo.dm"
#include "code\datums\supply_packs\attachments.dm"