From 439b7d0fb6be5c46d805db55ef1e2e90f4bba2ce Mon Sep 17 00:00:00 2001 From: monster860 Date: Tue, 1 Jan 2019 01:32:38 -0500 Subject: [PATCH] SPACEPODS! --- code/__DEFINES/is_helpers.dm | 4 +- code/__DEFINES/layers.dm | 1 + code/__DEFINES/~yogs_defines/spacepods.dm | 18 + code/_globalvars/lists/maintenance_loot.dm | 5 + code/game/atoms_movable.dm | 25 +- code/game/objects/items/stacks/rods.dm | 7 + .../living/simple_animal/hostile/hostile.dm | 8 +- .../hostile/retaliate/retaliate.dm | 6 + .../mob/living/simple_animal/simple_animal.dm | 6 + code/modules/projectiles/projectile.dm | 11 +- code/modules/research/machinery/protolathe.dm | 3 +- code/modules/research/machinery/techfab.dm | 3 +- yogstation.dme | 12 + yogstation/code/_onclick/adjacent.dm | 70 ++ .../code/game/machinery/doors/poddoor.dm | 60 ++ .../code/game/machinery/doors/spacepod.dm | 22 + .../research/designs/spacepod_designs.dm | 237 ++++++ .../modules/research/techweb/all_nodes.dm | 89 +++ .../code/modules/spacepods/construction.dm | 202 ++++++ .../code/modules/spacepods/equipment.dm | 349 +++++++++ yogstation/code/modules/spacepods/parts.dm | 168 +++++ yogstation/code/modules/spacepods/physics.dm | 295 ++++++++ yogstation/code/modules/spacepods/prebuilt.dm | 29 + yogstation/code/modules/spacepods/spacepod.dm | 683 ++++++++++++++++++ yogstation/goon/LICENSE.md | 4 + yogstation/goon/README.md | 8 + yogstation/goon/icons/obj/spacepods/2x2.dmi | Bin 0 -> 7679 bytes .../icons/obj/spacepods/construction_2x2.dmi | Bin 0 -> 5631 bytes yogstation/goon/icons/obj/spacepods/parts.dmi | Bin 0 -> 5007 bytes yogstation/icons/effects/beam.dmi | Bin 0 -> 1829 bytes yogstation/icons/obj/doors/1x2blast_hor.dmi | Bin 0 -> 1433 bytes yogstation/icons/obj/doors/1x2blast_vert.dmi | Bin 0 -> 1730 bytes yogstation/icons/obj/doors/1x3blast_hor.dmi | Bin 0 -> 1466 bytes yogstation/icons/obj/doors/1x3blast_vert.dmi | Bin 0 -> 2033 bytes yogstation/icons/obj/doors/1x4blast_hor.dmi | Bin 0 -> 1624 bytes yogstation/icons/obj/doors/1x4blast_vert.dmi | Bin 0 -> 2832 bytes yogstation/icons/obj/spacepods/2x2.dmi | Bin 0 -> 1085 bytes yogstation/icons/obj/spacepods/parts.dmi | Bin 0 -> 4712 bytes 38 files changed, 2312 insertions(+), 13 deletions(-) create mode 100644 code/__DEFINES/~yogs_defines/spacepods.dm create mode 100644 yogstation/code/_onclick/adjacent.dm create mode 100644 yogstation/code/game/machinery/doors/poddoor.dm create mode 100644 yogstation/code/game/machinery/doors/spacepod.dm create mode 100644 yogstation/code/modules/research/designs/spacepod_designs.dm create mode 100644 yogstation/code/modules/research/techweb/all_nodes.dm create mode 100644 yogstation/code/modules/spacepods/construction.dm create mode 100644 yogstation/code/modules/spacepods/equipment.dm create mode 100644 yogstation/code/modules/spacepods/parts.dm create mode 100644 yogstation/code/modules/spacepods/physics.dm create mode 100644 yogstation/code/modules/spacepods/prebuilt.dm create mode 100644 yogstation/code/modules/spacepods/spacepod.dm create mode 100644 yogstation/goon/LICENSE.md create mode 100644 yogstation/goon/README.md create mode 100644 yogstation/goon/icons/obj/spacepods/2x2.dmi create mode 100644 yogstation/goon/icons/obj/spacepods/construction_2x2.dmi create mode 100644 yogstation/goon/icons/obj/spacepods/parts.dmi create mode 100644 yogstation/icons/effects/beam.dmi create mode 100644 yogstation/icons/obj/doors/1x2blast_hor.dmi create mode 100644 yogstation/icons/obj/doors/1x2blast_vert.dmi create mode 100644 yogstation/icons/obj/doors/1x3blast_hor.dmi create mode 100644 yogstation/icons/obj/doors/1x3blast_vert.dmi create mode 100644 yogstation/icons/obj/doors/1x4blast_hor.dmi create mode 100644 yogstation/icons/obj/doors/1x4blast_vert.dmi create mode 100644 yogstation/icons/obj/spacepods/2x2.dmi create mode 100644 yogstation/icons/obj/spacepods/parts.dmi diff --git a/code/__DEFINES/is_helpers.dm b/code/__DEFINES/is_helpers.dm index 600fc8eb2669..1fcc2a4744e5 100644 --- a/code/__DEFINES/is_helpers.dm +++ b/code/__DEFINES/is_helpers.dm @@ -210,6 +210,8 @@ GLOBAL_LIST_INIT(heavyfootmob, typecacheof(list( #define ismecha(A) (istype(A, /obj/mecha)) +#define isspacepod(A) (istype(A, /obj/spacepod)) // yogs + #define is_cleanable(A) (istype(A, /obj/effect/decal/cleanable) || istype(A, /obj/effect/rune)) //if something is cleanable #define isorgan(A) (istype(A, /obj/item/organ)) @@ -247,4 +249,4 @@ GLOBAL_LIST_INIT(glass_sheet_types, typecacheof(list( #define isblobmonster(O) (istype(O, /mob/living/simple_animal/hostile/blob)) -#define isshuttleturf(T) (length(T.baseturfs) && (/turf/baseturf_skipover/shuttle in T.baseturfs)) \ No newline at end of file +#define isshuttleturf(T) (length(T.baseturfs) && (/turf/baseturf_skipover/shuttle in T.baseturfs)) diff --git a/code/__DEFINES/layers.dm b/code/__DEFINES/layers.dm index 08ffee272c0f..510eccca1cf6 100644 --- a/code/__DEFINES/layers.dm +++ b/code/__DEFINES/layers.dm @@ -55,6 +55,7 @@ #define BELOW_MOB_LAYER 3.7 #define LYING_MOB_LAYER 3.8 +#define SPACEPOD_LAYER 3.9 // yogs //#define MOB_LAYER 4 //For easy recordkeeping; this is a byond define #define ABOVE_MOB_LAYER 4.1 #define WALL_OBJ_LAYER 4.25 diff --git a/code/__DEFINES/~yogs_defines/spacepods.dm b/code/__DEFINES/~yogs_defines/spacepods.dm new file mode 100644 index 000000000000..a4bfbd5abf88 --- /dev/null +++ b/code/__DEFINES/~yogs_defines/spacepods.dm @@ -0,0 +1,18 @@ +#define SPACEPOD_EMPTY 1 +#define SPACEPOD_WIRES_LOOSE 2 +#define SPACEPOD_WIRES_SECURED 3 +#define SPACEPOD_CIRCUIT_LOOSE 4 +#define SPACEPOD_CIRCUIT_SECURED 5 +#define SPACEPOD_CORE_LOOSE 6 +#define SPACEPOD_CORE_SECURED 7 +#define SPACEPOD_BULKHEAD_LOOSE 8 +#define SPACEPOD_BULKHEAD_SECURED 9 +#define SPACEPOD_BULKHEAD_WELDED 10 +#define SPACEPOD_ARMOR_LOOSE 11 +#define SPACEPOD_ARMOR_SECURED 12 +#define SPACEPOD_ARMOR_WELDED 13 + +#define SPACEPOD_SLOT_CARGO "cargo" +#define SPACEPOD_SLOT_MISC "misc" +#define SPACEPOD_SLOT_WEAPON "weapon" +#define SPACEPOD_SLOT_LOCK "lock" diff --git a/code/_globalvars/lists/maintenance_loot.dm b/code/_globalvars/lists/maintenance_loot.dm index a06e03324685..bd746261c182 100644 --- a/code/_globalvars/lists/maintenance_loot.dm +++ b/code/_globalvars/lists/maintenance_loot.dm @@ -106,5 +106,10 @@ GLOBAL_LIST_INIT(maintenance_loot, list( /obj/item/storage/toolbox/artistic = 2, /obj/item/toy/eightball = 1, /obj/item/reagent_containers/pill/floorpill = 1, + // yogs start + /obj/item/pod_parts/core = 1, + /obj/item/pod_parts/armor = 1, + /obj/item/circuitboard/mecha/pod = 1, + // yogs end "" = 3 )) diff --git a/code/game/atoms_movable.dm b/code/game/atoms_movable.dm index 8fae785ca3ed..cfb674550fc2 100644 --- a/code/game/atoms_movable.dm +++ b/code/game/atoms_movable.dm @@ -209,11 +209,28 @@ direct = get_dir(src, newloc) setDir(direct) - if(!loc.Exit(src, newloc)) - return + // yogs start - multi tile object handling + if(bound_width != world.icon_size || bound_height != world.icon_size) + var/list/newlocs = isturf(newloc) ? block(locate(newloc.x+(-bound_x)/world.icon_size,newloc.y+(-bound_y)/world.icon_size,newloc.z),locate(newloc.x+(-bound_x+bound_width)/world.icon_size-1,newloc.y+(-bound_y+bound_height)/world.icon_size-1,newloc.z)) : list(newloc) + if(!newlocs) + return // we're trying to cross into the edge of space + var/bothturfs = isturf(newloc) && isturf(loc) + var/dx = bothturfs ? newloc.x - loc.x : 0 + var/dy = bothturfs ? newloc.y - loc.y : 0 + var/dz = bothturfs ? newloc.z - loc.z : 0 + for(var/atom/A in (locs - newlocs)) + if(!A.Exit(src, bothturfs ? locate(A.x+dx,A.y+dy,A.z+dz) : newloc)) + return + for(var/atom/A in (newlocs - locs)) + if(!A.Enter(src, bothturfs ? locate(A.x-dx,A.y-dy,A.z+dz) : loc)) + return + else + if(!loc.Exit(src, newloc)) + return - if(!newloc.Enter(src, src.loc)) - return + if(!newloc.Enter(src, src.loc)) + return + // yogs end // Past this is the point of no return var/atom/oldloc = loc diff --git a/code/game/objects/items/stacks/rods.dm b/code/game/objects/items/stacks/rods.dm index 2a54764024d6..4f3121801709 100644 --- a/code/game/objects/items/stacks/rods.dm +++ b/code/game/objects/items/stacks/rods.dm @@ -2,6 +2,13 @@ GLOBAL_LIST_INIT(rod_recipes, list ( \ new/datum/stack_recipe("grille", /obj/structure/grille, 2, time = 10, one_per_turf = TRUE, on_floor = FALSE), \ new/datum/stack_recipe("table frame", /obj/structure/table_frame, 2, time = 10, one_per_turf = 1, on_floor = 1), \ new/datum/stack_recipe("scooter frame", /obj/item/scooter_frame, 10, time = 25, one_per_turf = 0), \ + // yogs start + null, \ + new/datum/stack_recipe("fore port spacepod frame", /obj/item/pod_parts/pod_frame/fore_port, 15, time = 30, one_per_turf = 0), \ + new/datum/stack_recipe("fore starboard spacepod frame", /obj/item/pod_parts/pod_frame/fore_starboard, 15, time = 30, one_per_turf = 0), \ + new/datum/stack_recipe("aft port spacepod frame", /obj/item/pod_parts/pod_frame/aft_port, 15, time = 30, one_per_turf = 0), \ + new/datum/stack_recipe("aft starboard spacepod frame", /obj/item/pod_parts/pod_frame/aft_starboard, 15, time = 30, one_per_turf = 0), \ + // yogs end )) /obj/item/stack/rods diff --git a/code/modules/mob/living/simple_animal/hostile/hostile.dm b/code/modules/mob/living/simple_animal/hostile/hostile.dm index f2c9a6ef96ea..4925aa660d7e 100644 --- a/code/modules/mob/living/simple_animal/hostile/hostile.dm +++ b/code/modules/mob/living/simple_animal/hostile/hostile.dm @@ -100,7 +100,7 @@ if(!target || !isturf(target.loc) || !isturf(loc) || stat == DEAD) return var/target_dir = get_dir(src,target) - + var/static/list/cardinal_sidestep_directions = list(-90,-45,0,45,90) var/static/list/diagonal_sidestep_directions = list(-45,0,45) var/chosen_dir = 0 @@ -131,7 +131,7 @@ if(!search_objects) . = hearers(vision_range, targets_from) - src //Remove self, so we don't suicide - var/static/hostile_machines = typecacheof(list(/obj/machinery/porta_turret, /obj/mecha, /obj/structure/destructible/clockwork/ocular_warden)) + var/static/hostile_machines = typecacheof(list(/obj/machinery/porta_turret, /obj/mecha, /obj/structure/destructible/clockwork/ocular_warden, /obj/spacepod)) // yogs - add spacepod for(var/HM in typecache_filter_list(range(vision_range, targets_from), hostile_machines)) if(can_see(targets_from, HM, vision_range)) @@ -296,7 +296,7 @@ if(target) if(targets_from && isturf(targets_from.loc) && target.Adjacent(targets_from)) //If they're next to us, attack MeleeAction() - else + else if(rapid_melee > 1 && target_distance <= melee_queue_distance) MeleeAction(FALSE) in_melee = FALSE //If we're just preparing to strike do not enter sidestep mode @@ -563,7 +563,7 @@ mob/living/simple_animal/hostile/proc/DestroySurroundings() // for use with mega toggle_ai(AI_ON) /mob/living/simple_animal/hostile/proc/ListTargetsLazy(var/_Z)//Step 1, find out what we can see - var/static/hostile_machines = typecacheof(list(/obj/machinery/porta_turret, /obj/mecha, /obj/structure/destructible/clockwork/ocular_warden)) + var/static/hostile_machines = typecacheof(list(/obj/machinery/porta_turret, /obj/mecha, /obj/structure/destructible/clockwork/ocular_warden, /obj/spacepod)) // yogs - add spacepod . = list() for (var/I in SSmobs.clients_by_zlevel[_Z]) var/mob/M = I diff --git a/code/modules/mob/living/simple_animal/hostile/retaliate/retaliate.dm b/code/modules/mob/living/simple_animal/hostile/retaliate/retaliate.dm index 57df489d4ef3..2e8b1fa4179b 100644 --- a/code/modules/mob/living/simple_animal/hostile/retaliate/retaliate.dm +++ b/code/modules/mob/living/simple_animal/hostile/retaliate/retaliate.dm @@ -12,6 +12,12 @@ var/obj/mecha/M = A if(M.occupant) return A + // yogs start + else if(istype(A, /obj/spacepod)) + var/obj/spacepod/M = A + if(M.pilot || M.passengers.len) + return A + // yogs end /mob/living/simple_animal/hostile/retaliate/ListTargets() if(!enemies.len) diff --git a/code/modules/mob/living/simple_animal/simple_animal.dm b/code/modules/mob/living/simple_animal/simple_animal.dm index 151aad5cb5ee..84ec44985f79 100644 --- a/code/modules/mob/living/simple_animal/simple_animal.dm +++ b/code/modules/mob/living/simple_animal/simple_animal.dm @@ -342,6 +342,12 @@ var/obj/mecha/M = the_target if (M.occupant) return FALSE + // yogs start + if(isspacepod(the_target)) + var/obj/spacepod/SP = the_target + if(SP.pilot || SP.passengers.len) + return FALSE + // yogs end return TRUE /mob/living/simple_animal/handle_fire() diff --git a/code/modules/projectiles/projectile.dm b/code/modules/projectiles/projectile.dm index 9e14864c21a5..ec100babd082 100644 --- a/code/modules/projectiles/projectile.dm +++ b/code/modules/projectiles/projectile.dm @@ -247,9 +247,16 @@ return TRUE if(firer && !ignore_source_check) var/mob/checking = firer - if((A == firer) || (((A in firer.buckled_mobs) || (istype(checking) && (A == checking.buckled))) && (A != original)) || (A == firer.loc && ismecha(A))) //cannot shoot yourself or your mech + if((A == firer) || (((A in firer.buckled_mobs) || (istype(checking) && (A == checking.buckled))) && (A != original)) || (A == firer.loc && (ismecha(A) || isspacepod(A)))) //cannot shoot yourself or your mech // yogs - or your spacepod trajectory_ignore_forcemove = TRUE - forceMove(get_turf(A)) + // yogs start - multitile objects + var/turf/T = trajectory.return_turf() + if(!istype(T)) + qdel(src) + return + if(T != loc) + forceMove(get_step_towards(src, T)) + // yogs end trajectory_ignore_forcemove = FALSE return FALSE diff --git a/code/modules/research/machinery/protolathe.dm b/code/modules/research/machinery/protolathe.dm index e04261c961dd..ee9a1960fa5d 100644 --- a/code/modules/research/machinery/protolathe.dm +++ b/code/modules/research/machinery/protolathe.dm @@ -15,7 +15,8 @@ "Weapons", "Ammo", "Firing Pins", - "Computer Parts" + "Computer Parts", + "Spacepod Designs" // yogs ) production_animation = "protolathe_n" allowed_buildtypes = PROTOLATHE diff --git a/code/modules/research/machinery/techfab.dm b/code/modules/research/machinery/techfab.dm index 5525066f2ab6..447ab66fed65 100644 --- a/code/modules/research/machinery/techfab.dm +++ b/code/modules/research/machinery/techfab.dm @@ -26,7 +26,8 @@ "Subspace Telecomms", "Research Machinery", "Misc. Machinery", - "Computer Parts" + "Computer Parts", + "Spacepod Designs" // yogs ) console_link = FALSE production_animation = "protolathe_n" diff --git a/yogstation.dme b/yogstation.dme index 3398fed23c79..a2a5d6407485 100644 --- a/yogstation.dme +++ b/yogstation.dme @@ -118,6 +118,7 @@ #include "code\__DEFINES\~yogs_defines\mentor.dm" #include "code\__DEFINES\~yogs_defines\preferences.dm" #include "code\__DEFINES\~yogs_defines\shuttles.dm" +#include "code\__DEFINES\~yogs_defines\spacepods.dm" #include "code\__DEFINES\~yogs_defines\wires.dm" #include "code\__HELPERS\_lists.dm" #include "code\__HELPERS\_logging.dm" @@ -2750,6 +2751,7 @@ #include "yogstation\code\_globalvars\configuration.dm" #include "yogstation\code\_globalvars\logging.dm" #include "yogstation\code\_globalvars\lists\mobs.dm" +#include "yogstation\code\_onclick\adjacent.dm" #include "yogstation\code\_onclick\click.dm" #include "yogstation\code\_onclick\hud\vampire.dm" #include "yogstation\code\controllers\configuration\entries\general.dm" @@ -2803,6 +2805,8 @@ #include "yogstation\code\game\machinery\computer\medical.dm" #include "yogstation\code\game\machinery\computer\Operating.dm" #include "yogstation\code\game\machinery\doors\airlock.dm" +#include "yogstation\code\game\machinery\doors\poddoor.dm" +#include "yogstation\code\game\machinery\doors\spacepod.dm" #include "yogstation\code\game\machinery\pipe\construction.dm" #include "yogstation\code\game\machinery\telecomms\computers\logbrowser.dm" #include "yogstation\code\game\machinery\telecomms\computers\telemonitor.dm" @@ -2985,9 +2989,17 @@ #include "yogstation\code\modules\research\rdconsole.dm" #include "yogstation\code\modules\research\designs\bluespace_designs.dm" #include "yogstation\code\modules\research\designs\comp_board_designs.dm" +#include "yogstation\code\modules\research\designs\spacepod_designs.dm" #include "yogstation\code\modules\research\designs\stock_parts_designs.dm" #include "yogstation\code\modules\research\designs\tool_designs.dm" +#include "yogstation\code\modules\research\techweb\all_nodes.dm" #include "yogstation\code\modules\shuttle\emergency.dm" +#include "yogstation\code\modules\spacepods\construction.dm" +#include "yogstation\code\modules\spacepods\equipment.dm" +#include "yogstation\code\modules\spacepods\parts.dm" +#include "yogstation\code\modules\spacepods\physics.dm" +#include "yogstation\code\modules\spacepods\prebuilt.dm" +#include "yogstation\code\modules\spacepods\spacepod.dm" #include "yogstation\code\modules\spells\cluwnecurse.dm" #include "yogstation\code\modules\spells\spells.dm" #include "yogstation\code\modules\spells\spell_types\mime.dm" diff --git a/yogstation/code/_onclick/adjacent.dm b/yogstation/code/_onclick/adjacent.dm new file mode 100644 index 000000000000..3664701f94f1 --- /dev/null +++ b/yogstation/code/_onclick/adjacent.dm @@ -0,0 +1,70 @@ +/* + Adjacency (to turf): + * If you are in the same turf, always true + * If you are vertically/horizontally adjacent, ensure there are no border objects + * If you are diagonally adjacent, ensure you can pass through at least one of the mutually adjacent square. + * Passing through in this case ignores anything with the LETPASSTHROW pass flag, such as tables, racks, and morgue trays. +*/ +/turf/Adjacent(atom/neighbor, atom/target = null, atom/movable/mover = null) + if(neighbor == src) + return TRUE //don't be retarded!! + + if(istype(neighbor, /atom/movable)) //fml + var/atom/movable/AM = neighbor + if((AM.bound_width != world.icon_size || AM.bound_height != world.icon_size) && (islist(AM.locs) && AM.locs.len > 1)) + for(var/turf/T in AM.locs) + if(Adjacent(T, target, mover)) + return TRUE + return FALSE + + var/turf/T0 = get_turf(neighbor) + + if(T0 == src) //same turf + return TRUE + + if(get_dist(src, T0) > 1 || z != T0.z) //too far + return FALSE + + // Non diagonal case + if(T0.x == x || T0.y == y) + // Check for border blockages + return T0.ClickCross(get_dir(T0,src), border_only = 1, target_atom = target, mover = mover) && src.ClickCross(get_dir(src,T0), border_only = 1, target_atom = target, mover = mover) + + // Diagonal case + var/in_dir = get_dir(T0,src) // eg. northwest (1+8) = 9 (00001001) + var/d1 = in_dir&3 // eg. north (1+8)&3 (0000 0011) = 1 (0000 0001) + var/d2 = in_dir&12 // eg. west (1+8)&12 (0000 1100) = 8 (0000 1000) + + for(var/d in list(d1,d2)) + if(!T0.ClickCross(d, border_only = 1, target_atom = target, mover = mover)) + continue // could not leave T0 in that direction + + var/turf/T1 = get_step(T0,d) + if(!T1 || T1.density) + continue + if(!T1.ClickCross(get_dir(T1,src), border_only = 0, target_atom = target, mover = mover) || !T1.ClickCross(get_dir(T1,T0), border_only = 0, target_atom = target, mover = mover)) + continue // couldn't enter or couldn't leave T1 + + if(!src.ClickCross(get_dir(src,T1), border_only = 1, target_atom = target, mover = mover)) + continue // could not enter src + + return TRUE // we don't care about our own density + + return FALSE + +/* + Adjacency (to anything else): + * Must be on a turf +*/ +/atom/movable/Adjacent(var/atom/neighbor) + if(neighbor == loc) + return TRUE + if(!isturf(loc)) + return FALSE + if((islist(locs) && locs.len > 1) && (bound_width != world.icon_size || bound_height != world.icon_size)) + for(var/turf/T in locs) //this is to handle multi tile objects + if(T.Adjacent(neighbor, src, src)) + return TRUE + else if(loc.Adjacent(neighbor,target = neighbor, mover = src)) + return TRUE + return FALSE diff --git a/yogstation/code/game/machinery/doors/poddoor.dm b/yogstation/code/game/machinery/doors/poddoor.dm new file mode 100644 index 000000000000..323356b1f7ad --- /dev/null +++ b/yogstation/code/game/machinery/doors/poddoor.dm @@ -0,0 +1,60 @@ +/obj/machinery/door/poddoor/multi_tile + name = "large pod door" + layer = CLOSED_DOOR_LAYER + closingLayer = CLOSED_DOOR_LAYER + +/obj/machinery/door/poddoor/multi_tile/New() + . = ..() + apply_opacity_to_my_turfs(opacity) + +/obj/machinery/door/poddoor/multi_tile/open() + if(..()) + apply_opacity_to_my_turfs(opacity) + + +/obj/machinery/door/poddoor/multi_tile/close() + if(..()) + apply_opacity_to_my_turfs(opacity) + +/obj/machinery/door/poddoor/multi_tile/Destroy() + apply_opacity_to_my_turfs(0) + return ..() + +//Multi-tile poddoors don't turn invisible automatically, so we change the opacity of the turfs below instead one by one. +/obj/machinery/door/poddoor/multi_tile/proc/apply_opacity_to_my_turfs(var/new_opacity) + for(var/turf/T in locs) + T.opacity = new_opacity + T.has_opaque_atom = new_opacity + T.reconsider_lights() + T.air_update_turf(1) + update_freelook_sight() + +/obj/machinery/door/poddoor/multi_tile/four_tile_ver/ + icon = 'yogstation/icons/obj/doors/1x4blast_vert.dmi' + bound_height = 128 + dir = NORTH + +/obj/machinery/door/poddoor/multi_tile/three_tile_ver/ + icon = 'yogstation/icons/obj/doors/1x3blast_vert.dmi' + bound_height = 96 + dir = NORTH + +/obj/machinery/door/poddoor/multi_tile/two_tile_ver/ + icon = 'yogstation/icons/obj/doors/1x2blast_vert.dmi' + bound_height = 64 + dir = NORTH + +/obj/machinery/door/poddoor/multi_tile/four_tile_hor/ + icon = 'yogstation/icons/obj/doors/1x4blast_hor.dmi' + bound_width = 128 + dir = EAST + +/obj/machinery/door/poddoor/multi_tile/three_tile_hor/ + icon = 'yogstation/icons/obj/doors/1x3blast_hor.dmi' + bound_width = 96 + dir = EAST + +/obj/machinery/door/poddoor/multi_tile/two_tile_hor/ + icon = 'yogstation/icons/obj/doors/1x2blast_hor.dmi' + bound_width = 64 + dir = EAST diff --git a/yogstation/code/game/machinery/doors/spacepod.dm b/yogstation/code/game/machinery/doors/spacepod.dm new file mode 100644 index 000000000000..7b9c689ae38c --- /dev/null +++ b/yogstation/code/game/machinery/doors/spacepod.dm @@ -0,0 +1,22 @@ +/obj/structure/spacepoddoor + name = "podlock" + desc = "Why it no open!!!" + icon = 'yogstation/icons/effects/beam.dmi' + icon_state = "n_beam" + density = 1 + anchored = 1 + var/id = 1.0 + CanAtmosPass = ATMOS_PASS_NO + +/obj/structure/spacepoddoor/Initialize() + ..() + air_update_turf(1) + +/obj/structure/spacepoddoor/Destroy() + air_update_turf(1) + return ..() + +/obj/structure/spacepoddoor/CanPass(atom/movable/A, turf/T) + if(istype(A, /obj/spacepod)) + return TRUE + return ..() diff --git a/yogstation/code/modules/research/designs/spacepod_designs.dm b/yogstation/code/modules/research/designs/spacepod_designs.dm new file mode 100644 index 000000000000..c831f143912a --- /dev/null +++ b/yogstation/code/modules/research/designs/spacepod_designs.dm @@ -0,0 +1,237 @@ +/datum/design/board/spacepod_main + name = "Circuit Design (Space Pod Mainboard)" + desc = "Allows for the construction of a Space Pod mainboard." + id = "spacepod_main" + build_path = /obj/item/circuitboard/mecha/pod + category = list("Exosuit Modules") + departmental_flags = DEPARTMENTAL_FLAG_ALL + +/datum/design/pod_core + name = "Spacepod Core" + desc = "Allows for the construction of a spacepod core system, made up of the engine and life support systems." + id = "podcore" + build_type = PROTOLATHE + materials = list(MAT_METAL=5000, MAT_URANIUM=1000, MAT_PLASMA=5000) + build_path = /obj/item/pod_parts/core + category = list("Spacepod Designs") + departmental_flags = DEPARTMENTAL_FLAG_ALL + +/datum/design/pod_armor_civ + name = "Spacepod Armor (civilian)" + desc = "Allows for the construction of spcaepod armor. This is the civilian version." + id = "podarmor_civ" + build_type = PROTOLATHE + materials = list(MAT_METAL=15000,MAT_GLASS=5000,MAT_PLASMA=10000) + build_path = /obj/item/pod_parts/armor + category = list("Spacepod Designs") + departmental_flags = DEPARTMENTAL_FLAG_ALL + +/datum/design/pod_armor_black + name = "Spacepod Armor (dark)" + desc = "Allows for the construction of spacepod armor. This is the dark civillian version." + id = "podarmor_dark" + build_type = PROTOLATHE + build_path = /obj/item/pod_parts/armor/black + category = list("Spacepod Designs") + materials = list(MAT_METAL=15000,MAT_GLASS=5000,MAT_PLASMA=10000) + departmental_flags = DEPARTMENTAL_FLAG_ALL + +/datum/design/pod_armor_industrial + name = "Spacepod Armor (industrial)" + desc = "Allows for the construction of spacepod armor. This is the industrial grade version." + id = "podarmor_industiral" + build_type = PROTOLATHE + build_path = /obj/item/pod_parts/armor/industrial + category = list("Spacepod Designs") + materials = list(MAT_METAL=15000,MAT_GLASS=5000,MAT_PLASMA=10000,MAT_DIAMOND=5000,MAT_SILVER=7500) + departmental_flags = DEPARTMENTAL_FLAG_CARGO | DEPARTMENTAL_FLAG_ENGINEERING + +/datum/design/pod_armor_sec + name = "Spacepod Armor (security)" + desc = "Allows for the construction of spacepod armor. This is the security version." + id = "podarmor_sec" + build_type = PROTOLATHE + build_path = /obj/item/pod_parts/armor/security + category = list("Spacepod Designs") + materials = list(MAT_METAL=15000,MAT_GLASS=5000,MAT_PLASMA=10000,MAT_DIAMOND=5000,MAT_SILVER=7500) + departmental_flags = DEPARTMENTAL_FLAG_SECURITY + +/datum/design/pod_armor_gold + name = "Spacepod Armor (golden)" + desc = "Allows for the construction of spacepod armor. This is the golden version." + id = "podarmor_gold" + build_type = PROTOLATHE + build_path = /obj/item/pod_parts/armor/gold + category = list("Spacepod Designs") + materials = list(MAT_METAL=5000,MAT_GLASS=2500,MAT_PLASMA=7500,MAT_GOLD=10000) + departmental_flags = DEPARTMENTAL_FLAG_ALL + +////////////////////////////////////////// +//////SPACEPOD GUNS/////////////////////// +////////////////////////////////////////// + +/datum/design/pod_gun_disabler + name = "Spacepod Equipment (Disabler)" + desc = "Allows for the construction of a spacepod mounted disabler." + id = "podgun_disabler" + build_type = PROTOLATHE + build_path = /obj/item/spacepod_equipment/weaponry/disabler + category = list("Spacepod Designs") + materials = list(MAT_METAL = 15000) + departmental_flags = DEPARTMENTAL_FLAG_SECURITY + +/datum/design/pod_gun_bdisabler + name = "Spacepod Equipment (Burst Disabler)" + desc = "Allows for the construction of a spacepod mounted disabler. This is the burst-fire model." + id = "podgun_bdisabler" + build_type = PROTOLATHE + build_path = /obj/item/spacepod_equipment/weaponry/burst_disabler + category = list("Spacepod Designs") + materials = list(MAT_METAL = 15000,MAT_PLASMA=2000) + departmental_flags = DEPARTMENTAL_FLAG_SECURITY + +/datum/design/pod_gun_laser + name = "Spacepod Equipment (Laser)" + desc = "Allows for the construction of a spacepod mounted laser." + id = "podgun_laser" + build_type = PROTOLATHE + build_path = /obj/item/spacepod_equipment/weaponry/laser + category = list("Spacepod Designs") + materials = list(MAT_METAL=10000,MAT_GLASS=5000,MAT_GOLD=1000,MAT_SILVER=2000) + departmental_flags = DEPARTMENTAL_FLAG_SECURITY + +/datum/design/pod_ka_basic + name = "Spacepod Equipment (Basic Kinetic Accelerator)" + desc = "Allows for the construction of a weak spacepod Kinetic Accelerator" + id = "pod_ka_basic" + build_type = PROTOLATHE + materials = list(MAT_METAL = 10000, MAT_GLASS = 5000, MAT_SILVER = 2000, MAT_URANIUM = 2000) + build_path = /obj/item/spacepod_equipment/weaponry/basic_pod_ka + category = list("Spacepod Designs") + departmental_flags = DEPARTMENTAL_FLAG_CARGO + +/datum/design/pod_ka + name = "Spacepod Equipment (Kinetic Accelerator)" + desc = "Allows for the construction of a spacepod Kinetic Accelerator." + id = "pod_ka" + build_type = PROTOLATHE + materials = list(MAT_METAL = 10000, MAT_GLASS = 5000, MAT_SILVER = 2000, MAT_GOLD = 2000, MAT_DIAMOND = 2000) + build_path = /obj/item/spacepod_equipment/weaponry/pod_ka + category = list("Spacepod Designs") + departmental_flags = DEPARTMENTAL_FLAG_CARGO + + +/datum/design/pod_plasma_cutter + name = "Spacepod Equipment (Plasma Cutter)" + desc = "Allows for the construction of a plasma cutter." + id = "pod_plasma_cutter" + build_type = PROTOLATHE + materials = list(MAT_METAL = 10000, MAT_GLASS = 5000, MAT_SILVER = 2000, MAT_GOLD = 2000, MAT_DIAMOND = 2000) + build_path = /obj/item/spacepod_equipment/weaponry/plasma_cutter + category = list("Spacepod Designs") + departmental_flags = DEPARTMENTAL_FLAG_CARGO + +/datum/design/pod_adv_plasma_cutter + name = "Spacepod Equipment (Advanced Plasma cutter)" + desc = "Allows for the construction of an advanced plasma cutter." + id = "pod_adv_plasma_cutter" + build_type = PROTOLATHE + materials = list(MAT_METAL = 10000, MAT_GLASS = 5000, MAT_SILVER = 4000, MAT_GOLD = 4000, MAT_DIAMOND = 4000) + build_path = /obj/item/spacepod_equipment/weaponry/plasma_cutter/adv + category = list("Spacepod Designs") + departmental_flags = DEPARTMENTAL_FLAG_CARGO + +////////////////////////////////////////// +//////SPACEPOD MISC. ITEMS//////////////// +////////////////////////////////////////// + +/datum/design/pod_misc_tracker + name = "Spacepod Tracking Module" + desc = "Allows for the construction of a Space Pod Tracking Module." + id = "podmisc_tracker" + build_type = PROTOLATHE + materials = list(MAT_METAL=5000) + build_path = /obj/item/spacepod_equipment/tracker + category = list("Spacepod Designs") + departmental_flags = DEPARTMENTAL_FLAG_ALL + +////////////////////////////////////////// +//////SPACEPOD CARGO ITEMS//////////////// +////////////////////////////////////////// + +/datum/design/pod_cargo_ore + name = "Spacepod Ore Storage Module" + desc = "Allows for the construction of a Space Pod Ore Storage Module." + id = "podcargo_ore" + build_type = PROTOLATHE + materials = list(MAT_METAL=20000, MAT_GLASS=2000) + build_path = /obj/item/spacepod_equipment/cargo/large/ore + category = list("Spacepod Designs") + departmental_flags = DEPARTMENTAL_FLAG_CARGO + +/datum/design/pod_cargo_crate + name = "Spacepod Crate Storage Module" + desc = "Allows the construction of a Space Pod Crate Storage Module." + id = "podcargo_crate" + build_type = PROTOLATHE + materials = list(MAT_METAL=25000) + build_path = /obj/item/spacepod_equipment/cargo/large + category = list("Spacepod Designs") + departmental_flags = DEPARTMENTAL_FLAG_ALL + +////////////////////////////////////////// +//////SPACEPOD SEC CARGO ITEMS//////////// +////////////////////////////////////////// + +/datum/design/passenger_seat + name = "Spacepod Passenger Seat" + desc = "Allows the construction of a Space Pod Passenger Seat Module." + id = "podcargo_seat" + build_type = PROTOLATHE + materials = list(MAT_METAL=7500, MAT_GLASS=2500) + build_path = /obj/item/spacepod_equipment/cargo/chair + category = list("Spacepod Designs") + departmental_flags = DEPARTMENTAL_FLAG_ALL + +/*/datum/design/loot_box + name = "Spacepod Loot Storage Module" + desc = "Allows the construction of a Space Pod Auxillary Cargo Module." + id = "podcargo_lootbox" + build_type = PROTOLATHE + materials = list(MAT_METAL=7500, MAT_GLASS=2500) + build_path = /obj/item/spacepod_equipment/cargo/loot_box + category = list("Spacepod Designs") + departmental_flags = DEPARTMENTAL_FLAG_ALL*/ + +////////////////////////////////////////// +//////SPACEPOD LOCK ITEMS//////////////// +////////////////////////////////////////// +/datum/design/pod_lock_keyed + name = "Spacepod Tumbler Lock" + desc = "Allows for the construction of a tumbler style podlock." + id = "podlock_keyed" + build_type = PROTOLATHE + materials = list(MAT_METAL=4500) + build_path = /obj/item/spacepod_equipment/lock/keyed + category = list("Spacepod Designs") + departmental_flags = DEPARTMENTAL_FLAG_ALL + +/datum/design/pod_key + name = "Spacepod Tumbler Lock Key" + desc = "Allows for the construction of a blank key for a podlock." + id = "podkey" + build_type = PROTOLATHE + materials = list(MAT_METAL=500) + build_path = /obj/item/spacepod_key + category = list("Spacepod Designs") + departmental_flags = DEPARTMENTAL_FLAG_ALL + +/datum/design/lockbuster + name = "Spacepod Lock Buster" + desc = "Allows for the construction of a spacepod lockbuster." + id = "pod_lockbuster" + build_type = PROTOLATHE + build_path = /obj/item/device/lock_buster + category = list("Spacepod Designs") + materials = list(MAT_METAL = 15000, MAT_DIAMOND=2500) //it IS a drill! + departmental_flags = DEPARTMENTAL_FLAG_SECURITY diff --git a/yogstation/code/modules/research/techweb/all_nodes.dm b/yogstation/code/modules/research/techweb/all_nodes.dm new file mode 100644 index 000000000000..92ce59bb8052 --- /dev/null +++ b/yogstation/code/modules/research/techweb/all_nodes.dm @@ -0,0 +1,89 @@ +/datum/techweb_node/spacepod_basic + id = "spacepod_basic" + display_name = "Spacepod Construction" + description = "Basic stuff to construct Spacepods. Don't crash your first spacepod into the station, especially while going more than 10 m/s." + research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 2500) + export_price = 2500 + prereq_ids = list("base") + design_ids = list("podcore", "podarmor_civ", "podarmor_dark", "spacepod_main") + +/datum/techweb_node/spacepod_disabler + id = "spacepod_disabler" + display_name = "Spacepod Weaponry" + description = "For a bit of pew pew space battles" + research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 3500) + export_price = 3500 + prereq_ids = list("spacepod_basic", "weaponry") + design_ids = list("podgun_disabler") + +/datum/techweb_node/spacepod_lasers + id = "spacepod_lasers" + display_name = "Advanced Spacepod Weaponry" + description = "For a lot of pew pew space battles. PEW PEW PEW!! Shit, I missed. I need better aim. Whatever." + research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 5250) + export_price = 5250 + prereq_ids = list("spacepod_disabler", "electronic_weapons") + design_ids = list("podgun_laser", "podgun_bdisabler") + +/datum/techweb_node/spacepod_ka + id = "spacepod_ka" + display_name = "Spacepod Mining Tech" + description = "Cutting up asteroids using your spacepods" + research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 3500) + export_price = 500 + prereq_ids = list("basic_mining", "spacepod_disabler") + design_ids = list("pod_ka_basic") + +/datum/techweb_node/spacepod_advmining + id = "spacepod_aka" + display_name = "Advanced Spacepod Mining Tech" + description = "Cutting up asteroids using your spacepods.... faster!" + research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 3500) + export_price = 3500 + prereq_ids = list("spacepod_ka", "adv_mining") + design_ids = list("pod_ka", "pod_plasma_cutter") + +/datum/techweb_node/spacepod_advplasmacutter + id = "spacepod_apc" + display_name = "Advanced Spacepod Plasma Cutter" + description = "Cutting up asteroids using your spacepods........... FASTERRRRRR!!!!!! Oh shit, that was gibtonite." + research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 4500) + export_price = 4500 + prereq_ids = list("spacepod_aka", "adv_plasma") + design_ids = list("pod_adv_plasma_cutter") + +/datum/techweb_node/spacepod_pseat + id = "spacepod_pseat" + display_name = "Spacepod Passenger Seat" + description = "For bringing along victims as you fly off into the far reaches of space" + research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 3750) + export_price = 3750 + prereq_ids = list("spacepod_basic", "adv_engi") + design_ids = list("podcargo_seat") + +/datum/techweb_node/spacepod_storage + id = "spacepod_storage" + display_name = "Spacepod Storage" + description = "For storing the stuff you find in the far reaches of space" + research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 4500) + export_price = 4500 + prereq_ids = list("spacepod_pseat", "high_efficiency") + design_ids = list("podcargo_lootbox", "podcargo_crate", "podcargo_ore") + +/datum/techweb_node/spacepod_lockbuster + id = "spacepod_lockbuster" + display_name = "Spacepod Lock Buster" + description = "For when someone's being really naughty with a spacepod" + research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 8500) + export_price = 8500 + prereq_ids = list("spacepod_lasers", "high_efficiency", "adv_mining") + design_ids = list("pod_lockbuster") + +/datum/techweb_node/spacepod_iarmor + id = "spacepod_iarmor" + display_name = "Advanced Spacepod Armor" + description = "Better protection for your precious ride. You'll need it if you plan on engaging in spacepod battles." + research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 2750) + export_price = 2750 + prereq_ids = list("spacepod_storage", "high_efficiency") + design_ids = list("podarmor_industiral", "podarmor_sec", "podarmor_gold") diff --git a/yogstation/code/modules/spacepods/construction.dm b/yogstation/code/modules/spacepods/construction.dm new file mode 100644 index 000000000000..3467f54db279 --- /dev/null +++ b/yogstation/code/modules/spacepods/construction.dm @@ -0,0 +1,202 @@ +/obj/spacepod/examine(mob/user) + ..() + switch(construction_state) // more construction states than r-walls! + if(SPACEPOD_EMPTY) + to_chat(user, "The strits holding it together can be cut and i't is missing wires.") + if(SPACEPOD_WIRES_LOOSE) + to_chat(user, "The wires need to be screwed on.") + if(SPACEPOD_WIRES_SECURED) + to_chat(user, "The wires are screwed on and needs a circuit board.") + if(SPACEPOD_CIRCUIT_LOOSE) + to_chat(user, "The circuit board is loosely attached and needs to be screwed on.") + if(SPACEPOD_CIRCUIT_SECURED) + to_chat(user, "The circuit board is screwed on, and there is space for a core.") + if(SPACEPOD_CORE_LOOSE) + to_chat(user, "The core is loosely attached and needs to be bolted on.") + if(SPACEPOD_CORE_SECURED) + to_chat(user, "The core is bolted on and the metal bulkhead can be attached.") + if(SPACEPOD_BULKHEAD_LOOSE) + to_chat(user, "The bulkhead is loosely attached and can be bolted down.") + if(SPACEPOD_BULKHEAD_SECURED) + to_chat(user, "The bulkhead is bolted on but not welded on.") + if(SPACEPOD_BULKHEAD_WELDED) + to_chat(user, "The bulkhead is welded on and armor can be attached.") + if(SPACEPOD_ARMOR_LOOSE) + to_chat(user, "The armor is loosely attached and can be bolted down.") + if(SPACEPOD_ARMOR_SECURED) + to_chat(user, "The armor is bolted on but not welded on.") + if(SPACEPOD_ARMOR_WELDED) + if(hatch_open) + if(cell || internal_tank || equipment.len) + to_chat(user, "The maintenance hatch is pried and there are parts inside that can be removed.") + else + to_chat(user, "The maintenance hatch is pried open and the armor is welded on.") + else + if(locked) + to_chat(user, "[src] is locked.") + else + to_chat(user, "The maintenance hatch is pried closed.") + +/obj/spacepod/proc/handle_spacepod_construction(obj/item/W, mob/living/user) + // time for a construction/deconstruction process to rival r-walls + var/obj/item/stack/ST = W + switch(construction_state) + if(SPACEPOD_EMPTY) + if(W.tool_behaviour == TOOL_WIRECUTTER) + . = TRUE + user.visible_message("[user] deconstructs [src].", "You deconstruct [src].") + deconstruct(TRUE) + else if(istype(W, /obj/item/stack/cable_coil)) + . = TRUE + if(ST.use(10)) + user.visible_message("[user] wires [src].", "You wire [src].") + construction_state++ + else + to_chat(user, "You need 10 wires for this!") + if(SPACEPOD_WIRES_LOOSE) + if(W.tool_behaviour == TOOL_WIRECUTTER) + . = TRUE + var/obj/item/stack/cable_coil/CC = new + CC.amount = 10 + CC.forceMove(loc) + W.play_tool_sound(src) + construction_state-- + user.visible_message("[user] cuts [src]'s wiring.", "You remove [src]'s wiring.") + else if(W.tool_behaviour == TOOL_SCREWDRIVER) + . = TRUE + W.play_tool_sound(src) + construction_state++ + user.visible_message("[user] adjusts the wiring.", "You adjust [src]'s wiring.") + if(SPACEPOD_WIRES_SECURED) + if(W.tool_behaviour == TOOL_SCREWDRIVER) + . = TRUE + W.play_tool_sound(src) + construction_state-- + user.visible_message("[user] unclips [src]'s wiring harnesses.", "You unclip [src]'s wiring harnesses.") + else if(istype(W, /obj/item/circuitboard/mecha/pod)) + . = TRUE + if(user.temporarilyRemoveItemFromInventory(W)) + qdel(W) + construction_state++ + user.visible_message("[user] inserts the mainboard into [src].", "You insert the mainboard into [src].") + else + to_chat(user, "[W] is stuck to your hand!") + if(SPACEPOD_CIRCUIT_LOOSE) + if(W.tool_behaviour == TOOL_CROWBAR) + . = TRUE + W.play_tool_sound(src) + construction_state-- + var/obj/item/circuitboard/mecha/pod/B = new + B.forceMove(loc) + user.visible_message("[user] pries out the mainboard.", "You pry out the mainboard.") + else if(W.tool_behaviour == TOOL_SCREWDRIVER) + . = TRUE + W.play_tool_sound(src) + construction_state++ + user.visible_message("[user] secures the mainboard.", "You secure the mainboard.") + if(SPACEPOD_CIRCUIT_SECURED) + if(W.tool_behaviour == TOOL_SCREWDRIVER) + . = TRUE + W.play_tool_sound(src) + construction_state-- + user.visible_message("[user] unsecures the mainboard.", "You unscrew the mainboard from [src].") + else if(istype(W, /obj/item/pod_parts/core)) + . = TRUE + if(user.temporarilyRemoveItemFromInventory(W)) + qdel(W) + construction_state++ + user.visible_message("[user] inserts the core into [src].", "You carefully insert the core into [src].") + else + to_chat(user, "[W] is stuck to your hand!") + if(SPACEPOD_CORE_LOOSE) + if(W.tool_behaviour == TOOL_CROWBAR) + . = TRUE + W.play_tool_sound(src) + var/obj/item/pod_parts/core/C = new + C.forceMove(loc) + construction_state-- + user.visible_message("[user] delicately removes the core from [src] with a crowbar.", "You delicately remove the core from [src] with a crowbar.") + else if(W.tool_behaviour == TOOL_WRENCH) + . = TRUE + W.play_tool_sound(src) + construction_state++ + user.visible_message("[user] secures the core's bolts.", "You secure the core's bolts.") + if(SPACEPOD_CORE_SECURED) + if(W.tool_behaviour == TOOL_WRENCH) + . = TRUE + W.play_tool_sound(src) + construction_state-- + user.visible_message("[user] unsecures [src]'s core.", "You unsecure [src]'s core.") + else if(istype(W, /obj/item/stack/sheet/metal)) + . = TRUE + if(ST.use(5)) + user.visible_message("[user] fabricates a pressure bulkhead for [src].", "You frabricate a pressure bulkhead for [src].") + construction_state++ + else + to_chat(user, "You need 5 metal for this!") + if(SPACEPOD_BULKHEAD_LOOSE) + if(W.tool_behaviour == TOOL_CROWBAR) + . = TRUE + W.play_tool_sound(src) + construction_state-- + var/obj/item/stack/sheet/metal/five/M = new + M.forceMove(loc) + user.visible_message("[user] pops [src]'s bulkhead panelling loose.", "You pop [src]'s bulkhead panelling loose.") + else if(W.tool_behaviour == TOOL_WRENCH) + . = TRUE + W.play_tool_sound(src) + construction_state++ + user.visible_message("[user] secures [src]'s bulkhead panelling.", "You secure [src]'s bulkhead panelling.") + if(SPACEPOD_BULKHEAD_SECURED) + if(W.tool_behaviour == TOOL_WRENCH) + . = TRUE + W.play_tool_sound(src) + construction_state-- + user.visible_message("[user] unbolts [src]'s bulkhead panelling.", "You unbolt [src]'s bulkhead panelling.") + else if(W.tool_behaviour == TOOL_WELDER) + . = TRUE + if(W.use_tool(src, user, 20, amount=3, volume = 50)) + construction_state = SPACEPOD_ARMOR_WELDED + user.visible_message("[user] seals [src]'s bulkhead panelling with a weld.", "You seal [src]'s bulkhead panelling with a weld.") + if(SPACEPOD_BULKHEAD_WELDED) + if(W.tool_behaviour == TOOL_WELDER) + . = TRUE + if(W.use_tool(src, user, 20, amount=3, volume = 50)) + construction_state = SPACEPOD_BULKHEAD_SECURED + user.visible_message("[user] cuts [src]'s bulkhead panelling loose.", "You cut [src]'s bulkhead panelling loose.") + if(istype(W, /obj/item/pod_parts/armor)) + . = TRUE + if(user.transferItemToLoc(W, src)) + add_armor(W) + construction_state++ + user.visible_message("[user] installs [src]'s armor plating.", "You install [src]'s armor plating.") + else + to_chat(user, "[W] is stuck to your hand!") + if(SPACEPOD_ARMOR_LOOSE) + if(W.tool_behaviour == TOOL_CROWBAR) + . = TRUE + if(pod_armor) + pod_armor.forceMove(loc) + remove_armor() + W.play_tool_sound(src) + construction_state-- + user.visible_message("[user] pries off [src]'s armor.", "You pry off [src]'s armor.") + if(W.tool_behaviour == TOOL_WRENCH) + . = TRUE + W.play_tool_sound(src) + construction_state++ + user.visible_message("[user] bolts down [src]'s armor.", "You bolt down [src]'s armor.") + if(SPACEPOD_ARMOR_SECURED) + if(W.tool_behaviour == TOOL_WRENCH) + . = TRUE + W.play_tool_sound(src) + construction_state-- + user.visible_message("[user] unsecures [src]'s armor.", "You unsecure [src]'s armor.") + else if(W.tool_behaviour == TOOL_WELDER) + . = TRUE + if(W.use_tool(src, user, 50, amount=3, volume = 50)) + construction_state = SPACEPOD_ARMOR_WELDED + user.visible_message("[user] welds [src]'s armor.", "You weld [src]'s armor.") + // finally this took too fucking long + // somehow this takes up 40 lines less code than the original, code-less version. And it actually works + update_icon() diff --git a/yogstation/code/modules/spacepods/equipment.dm b/yogstation/code/modules/spacepods/equipment.dm new file mode 100644 index 000000000000..6f84897079e7 --- /dev/null +++ b/yogstation/code/modules/spacepods/equipment.dm @@ -0,0 +1,349 @@ +/obj/item/spacepod_equipment + var/obj/spacepod/spacepod + icon = 'yogstation/icons/obj/spacepods/parts.dmi' + var/slot = SPACEPOD_SLOT_MISC + var/slot_space = 1 + +/obj/item/spacepod_equipment/proc/on_install(obj/spacepod/SP) + spacepod = SP + SP.equipment |= src + forceMove(SP) + +/obj/item/spacepod_equipment/proc/on_uninstall() + spacepod.equipment -= src + +/obj/item/spacepod_equipment/proc/can_install(obj/spacepod/SP, mob/user) + var/room = SP.equipment_slot_limits[slot] || 0 + for(var/obj/item/spacepod_equipment/EQ in SP.equipment) + if(EQ.slot == slot) + room -= EQ.slot_space + if(room < slot_space) + to_chat(user, "There's no room for another [slot] system!") + return FALSE + return TRUE + +/obj/item/spacepod_equipment/proc/can_uninstall(mob/user) + return TRUE + +/obj/item/spacepod_equipment/weaponry + slot = SPACEPOD_SLOT_WEAPON + var/projectile_type + var/shot_cost = 0 + var/shots_per = 1 + var/fire_sound + var/fire_delay = 15 + var/overlay_icon + var/overlay_icon_state + +/obj/item/spacepod_equipment/weaponry/on_install(obj/spacepod/SP) + . = ..() + SP.weapon = src + SP.update_icon() + +/obj/item/spacepod_equipment/weaponry/on_uninstall() + . = ..() + if(spacepod.weapon == src) + spacepod.weapon = null + +/obj/item/spacepod_equipment/weaponry/proc/fire_weapons(target) + if(spacepod.next_firetime > world.time) + to_chat(usr, "Your weapons are recharging.") + playsound(src, 'sound/weapons/gun_dry_fire.ogg', 30, TRUE) + return + spacepod.next_firetime = world.time + fire_delay + if(!spacepod.cell || !spacepod.cell.use(shot_cost)) + to_chat(usr, "Insufficient charge to fire the weapons") + playsound(src, 'sound/weapons/gun_dry_fire.ogg', 30, TRUE) + return + for(var/I in 1 to shots_per) + spacepod.fire_projectiles(projectile_type, target) + playsound(src, fire_sound, 50, TRUE) + sleep(2) + +/* +/////////////////////////////////////// +/////////Cargo System////////////////// +/////////////////////////////////////// +*/ + +/obj/item/spacepod_equipment/cargo // this one holds large crates and shit + name = "pod cargo" + desc = "You shouldn't be seeing this" + icon_state = "cargo_blank" + slot = SPACEPOD_SLOT_CARGO + +/obj/item/spacepod_equipment/cargo/large + name = "spacepod crate storage system" + desc = "A heavy duty storage system for spacepods. Holds one crate." + icon_state = "cargo_crate" + var/obj/storage = null + var/storage_type = /obj/structure/closet/crate + +/obj/item/spacepod_equipment/cargo/large/on_install(obj/spacepod/SP) + ..() + // COMSIG - a way to make component signals sound more important than they actually are. + // it's not even limited to components. Does this look like a component to you? + // Okay here's a better name: It's a fucking *event handler*. Like the ones in javascript. + // a much more descriptive and less scary name than fucking "COMSIG". But noooooooooo + // the TG coders were too self important to pick a descriptive name and wanted to sound all scientific + RegisterSignal(SP, COMSIG_MOUSEDROPPED_ONTO, .proc/spacepod_mousedrop) + +/obj/item/spacepod_equipment/cargo/large/on_uninstall() + UnregisterSignal(spacepod, COMSIG_MOUSEDROPPED_ONTO) + ..() + +/obj/item/spacepod_equipment/cargo/large/can_uninstall(mob/user) + if(storage) + to_chat(user, "Unload the cargo first!") + return FALSE + return ..() + +/obj/item/spacepod_equipment/cargo/large/proc/spacepod_mousedrop(obj/spacepod/SP, obj/A, mob/user) + if(user == SP.pilot || user in SP.passengers) + return FALSE + if(istype(A, storage_type) && SP.Adjacent(A)) // For loading ore boxes + if(!storage) + to_chat(user, "You begin loading [A] into [SP]'s [src]") + if(do_after_mob(user, list(A, SP), 40)) + storage = A + A.forceMove(src) + to_chat(user, "You load [A] into [SP]'s [src]!") + else + to_chat(user, "You fail to load [A] into [SP]'s [src]") + else + to_chat(user, "[SP] already has \an [storage]") + return TRUE + return FALSE + +/obj/item/spacepod_equipment/cargo/large/ore + name = "spacepod ore storage system" + desc = "An ore storage system for spacepods. Scoops up any ore you drive over. Needs to be loaded with an ore box to work" + icon_state = "cargo_ore" + storage_type = /obj/structure/ore_box + +/obj/item/spacepod_equipment/cargo/large/ore/on_install(obj/spacepod/SP) + ..() + RegisterSignal(SP, COMSIG_MOVABLE_MOVED, .proc/spacepod_moved) + +/obj/item/spacepod_equipment/cargo/large/ore/on_uninstall() + UnregisterSignal(spacepod, COMSIG_MOVABLE_MOVED) + ..() + +/obj/item/spacepod_equipment/cargo/large/ore/proc/spacepod_moved(obj/spacepod/SP) + if(storage) + for(var/turf/T in SP.locs) + for(var/obj/item/stack/ore in T) + ore.forceMove(storage) + +/obj/item/spacepod_equipment/cargo/chair + name = "passenger seat" + desc = "A passenger seat for a spacepod." + icon_state = "sec_cargo_chair" + var/occupant_mod = 1 + +/obj/item/spacepod_equipment/cargo/chair/on_install(obj/spacepod/SP) + ..() + SP.max_passengers += occupant_mod + +/obj/item/spacepod_equipment/cargo/chair/on_uninstall() + spacepod.max_passengers -= occupant_mod + ..() + +/obj/item/spacepod_equipment/cargo/chair/can_uninstall(mob/user) + if(spacepod.passengers.len > (spacepod.max_passengers - occupant_mod)) + to_chat(user, "You can't remove an occupied seat! Remove the occupant first.") + return FALSE + return ..() + +/* +/////////////////////////////////////// +/////////Weapon System/////////////////// +/////////////////////////////////////// +*/ + +/obj/item/spacepod_equipment/weaponry/disabler + name = "disabler system" + desc = "A weak disabler system for space pods, fires disabler beams." + icon_state = "weapon_taser" + projectile_type = /obj/item/projectile/beam/disabler + shot_cost = 400 + fire_sound = 'sound/weapons/taser2.ogg' + overlay_icon = 'yogstation/icons/obj/spacepods/2x2.dmi' + overlay_icon_state = "pod_weapon_disabler" + +/obj/item/spacepod_equipment/weaponry/burst_disabler + name = "burst disabler system" + desc = "A weak disabler system for space pods, this one fires 3 at a time." + icon_state = "weapon_burst_taser" + projectile_type = /obj/item/projectile/beam/disabler + shot_cost = 1200 + shots_per = 3 + fire_sound = 'sound/weapons/taser2.ogg' + fire_delay = 30 + overlay_icon = 'yogstation/icons/obj/spacepods/2x2.dmi' + overlay_icon_state = "pod_weapon_disabler" + +/obj/item/spacepod_equipment/weaponry/laser + name = "laser system" + desc = "A weak laser system for space pods, fires concentrated bursts of energy." + icon_state = "weapon_laser" + projectile_type = /obj/item/projectile/beam + shot_cost = 600 + fire_sound = 'sound/weapons/Laser.ogg' + overlay_icon = 'yogstation/icons/obj/spacepods/2x2.dmi' + overlay_icon_state = "pod_weapon_laser" + +// MINING LASERS +/obj/item/spacepod_equipment/weaponry/basic_pod_ka + name = "weak kinetic accelerator" + desc = "A weak kinetic accelerator for space pods, fires bursts of energy that cut through rock." + icon = 'yogstation/goon/icons/obj/spacepods/parts.dmi' + icon_state = "pod_taser" + projectile_type = /obj/item/projectile/kinetic/pod + shot_cost = 300 + fire_delay = 14 + fire_sound = 'sound/weapons/Kenetic_accel.ogg' + +/obj/item/spacepod_equipment/weaponry/pod_ka + name = "kinetic accelerator system" + desc = "A kinetic accelerator system for space pods, fires bursts of energy that cut through rock." + icon = 'yogstation/goon/icons/obj/spacepods/parts.dmi' + icon_state = "pod_m_laser" + projectile_type = /obj/item/projectile/kinetic/pod/regular + shot_cost = 250 + fire_delay = 10 + fire_sound = 'sound/weapons/Kenetic_accel.ogg' + +/obj/item/projectile/kinetic/pod + range = 4 + +/obj/item/projectile/kinetic/pod/regular + damage = 50 + pressure_decrease = 0.5 + +/obj/item/spacepod_equipment/weaponry/plasma_cutter + name = "plasma cutter system" + desc = "A plasma cutter system for space pods. It is capable of expelling concentrated plasma bursts to mine or cut off xeno limbs!" + icon = 'yogstation/goon/icons/obj/spacepods/parts.dmi' + icon_state = "pod_p_cutter" + projectile_type = /obj/item/projectile/plasma + shot_cost = 250 + fire_delay = 10 + fire_sound = 'sound/weapons/plasma_cutter.ogg' + overlay_icon = 'yogstation/icons/obj/spacepods/2x2.dmi' + overlay_icon_state = "pod_weapon_plasma" + +/obj/item/spacepod_equipment/weaponry/plasma_cutter/adv + name = "enhanced plasma cutter system" + desc = "An enhanced plasma cutter system for space pods. It is capable of expelling concentrated plasma bursts to mine or cut off xeno faces!" + icon_state = "pod_ap_cutter" + projectile_type = /obj/item/projectile/plasma/adv + shot_cost = 200 + fire_delay = 8 + +/* +/////////////////////////////////////// +/////////Misc. System/////////////////// +/////////////////////////////////////// +*/ + +/obj/item/spacepod_equipment/tracker + name = "spacepod tracking system" + desc = "A tracking device for spacepods." + icon = 'yogstation/goon/icons/obj/spacepods/parts.dmi' + icon_state = "pod_locator" + +/* +/////////////////////////////////////// +/////////Lock System/////////////////// +/////////////////////////////////////// +*/ + +/obj/item/spacepod_equipment/lock + name = "pod lock" + desc = "You shouldn't be seeing this" + icon_state = "blank" + slot = SPACEPOD_SLOT_LOCK + +/obj/item/spacepod_equipment/lock/on_install(obj/spacepod/SP) + ..() + RegisterSignal(SP, COMSIG_PARENT_ATTACKBY, .proc/spacepod_attackby) + SP.lock = src + +/obj/item/spacepod_equipment/lock/on_uninstall() + UnregisterSignal(spacepod, COMSIG_PARENT_ATTACKBY) + if(spacepod.lock == src) + spacepod.lock = null + spacepod.locked = FALSE + ..() + +/obj/item/spacepod_equipment/lock/proc/spacepod_attackby(obj/spacepod/SP, I, mob/user) + return FALSE + +// Key and Tumbler System +/obj/item/spacepod_equipment/lock/keyed + name = "spacepod tumbler lock" + desc = "A locking system to stop podjacking. This version uses a standalone key." + icon_state = "lock_tumbler" + var/static/id_source = 0 + var/id = null + +/obj/item/spacepod_equipment/lock/keyed/Initialize() + . = ..() + if(id == null) + id = ++id_source + + +/obj/item/spacepod_equipment/lock/keyed/spacepod_attackby(obj/spacepod/SP, obj/item/I, mob/user) + if(istype(I, /obj/item/spacepod_key)) + var/obj/item/spacepod_key/key = I + if(key.id == id) + SP.lock_pod() + return + else + to_chat(user, "This is the wrong key!") + return TRUE + return FALSE + +/obj/item/spacepod_equipment/lock/keyed/attackby(obj/item/I, mob/user) + if(istype(I, /obj/item/spacepod_key)) + var/obj/item/spacepod_key/key = I + if(key.id == null) + key.id = id + to_chat(user, "You grind the blank key to fit the lock.") + else + to_chat(user, "This key is already ground!") + else + ..() + +/obj/item/spacepod_equipment/lock/keyed/sec + id = "security spacepod" + +// The key +/obj/item/spacepod_key + name = "spacepod key" + desc = "A key for a spacepod lock." + icon = 'yogstation/icons/obj/spacepods/parts.dmi' + icon_state = "podkey" + w_class = WEIGHT_CLASS_TINY + var/id = null + +/obj/item/spacepod_key/sec + name = "security spacepod key" + desc = "Unlocks the security spacepod. Probably best kept out of assistant hands." + id = "security spacepod" + +/obj/item/device/lock_buster + name = "pod lock buster" + desc = "Destroys a podlock in mere seconds once applied. Waranty void if used." + icon = 'yogstation/icons/obj/spacepods/parts.dmi' + icon_state = "lock_buster_off" + var/on = FALSE + +/obj/item/device/lock_buster/attack_self(mob/user) + on = !on + if(on) + icon_state = "lock_buster_on" + else + icon_state = "lock_buster_off" + to_chat(user, "You turn the [src] [on ? "on" : "off"].") diff --git a/yogstation/code/modules/spacepods/parts.dm b/yogstation/code/modules/spacepods/parts.dm new file mode 100644 index 000000000000..00836229b329 --- /dev/null +++ b/yogstation/code/modules/spacepods/parts.dm @@ -0,0 +1,168 @@ +/obj/item/pod_parts + icon = 'yogstation/goon/icons/obj/spacepods/parts.dmi' + w_class = WEIGHT_CLASS_GIGANTIC + flags_1 = CONDUCT_1 + +/obj/item/pod_parts/core + name = "space pod core" + icon_state = "core" + +/obj/item/pod_parts/pod_frame + name = "space pod frame" + density = 0 + anchored = 0 + var/link_to = null + var/link_angle = 0 + +/obj/item/pod_parts/pod_frame/ComponentInitialize() + AddComponent(/datum/component/simple_rotation, ROTATION_ALTCLICK | ROTATION_CLOCKWISE) + +/obj/item/pod_parts/pod_frame/proc/find_square() + /* + each part, in essence, stores the relative position of another part + you can find where this part should be by looking at the current direction of the current part and applying the link_angle + the link_angle is the angle between the part's direction and its following part, which is the current part's link_to + the code works by going in a loop - each part is capable of starting a loop by checking for the part after it, and that part checking, and so on + this 4-part loop, starting from any part of the frame, can determine if all the parts are properly in place and aligned + it also checks that each part is unique, and that all the parts are there for the spacepod itself + */ + var/neededparts = list(/obj/item/pod_parts/pod_frame/aft_port, /obj/item/pod_parts/pod_frame/aft_starboard, /obj/item/pod_parts/pod_frame/fore_port, /obj/item/pod_parts/pod_frame/fore_starboard) + var/turf/T + var/obj/item/pod_parts/pod_frame/linked + var/obj/item/pod_parts/pod_frame/pointer + var/list/connectedparts = list() + neededparts -= src + linked = src + for(var/i = 1; i <= 4; i++) + T = get_turf(get_step(linked, turn(linked.dir, -linked.link_angle))) //get the next place that we want to look at + if(locate(linked.link_to) in T) + pointer = locate(linked.link_to) in T + if(istype(pointer, linked.link_to) && pointer.dir == linked.dir && pointer.anchored) + if(!(pointer in connectedparts)) + connectedparts += pointer + linked = pointer + pointer = null + if(connectedparts.len < 4) + return 0 + for(var/i = 1; i <=4; i++) + var/obj/item/pod_parts/pod_frame/F = connectedparts[i] + if(F.type in neededparts) //if one of the items can be founded in neededparts + neededparts -= F.type + else //because neededparts has 4 distinct items, this must be called if theyre not all in place and wrenched + return 0 + return connectedparts + +/obj/item/pod_parts/pod_frame/attackby(var/obj/item/O, mob/user) + if(istype(O, /obj/item/stack/rods)) + var/obj/item/stack/rods/R = O + var/list/linkedparts = find_square() + if(!linkedparts) + to_chat(user, "You cannot assemble a pod frame because you do not have the necessary assembly.") + return TRUE + if(!R.use(10)) + to_chat(user, "You need 10 rods for this.") + return TRUE + var/obj/spacepod/pod = new + pod.forceMove(loc) + switch(dir) + if(NORTH) + pod.angle = 0 + if(SOUTH) + pod.angle = 180 + if(WEST) + pod.angle = 270 + if(EAST) + pod.angle = 90 + pod.process(2) + to_chat(user, "You strut the pod frame together.") + for(var/obj/item/pod_parts/pod_frame/F in linkedparts) + if(1 == turn(F.dir, -F.link_angle)) //if the part links north during construction, as the bottom left part always does + pod.forceMove(F.loc) + qdel(F) + return TRUE + if(O.tool_behaviour == TOOL_WRENCH) + to_chat(user, "You [!anchored ? "secure \the [src] in place." : "remove the securing bolts."]") + anchored = !anchored + density = anchored + O.play_tool_sound(src) + return TRUE + +/obj/item/pod_parts/pod_frame/fore_port + name = "fore port pod frame" + icon_state = "pod_fp" + desc = "A space pod frame component. This is the fore port component." + link_to = /obj/item/pod_parts/pod_frame/fore_starboard + link_angle = 90 + +/obj/item/pod_parts/pod_frame/fore_starboard + name = "fore starboard pod frame" + icon_state = "pod_fs" + desc = "A space pod frame component. This is the fore starboard component." + link_to = /obj/item/pod_parts/pod_frame/aft_starboard + link_angle = 180 + +/obj/item/pod_parts/pod_frame/aft_port + name = "aft port pod frame" + icon_state = "pod_ap" + desc = "A space pod frame component. This is the aft port component." + link_to = /obj/item/pod_parts/pod_frame/fore_port + link_angle = 0 + +/obj/item/pod_parts/pod_frame/aft_starboard + name = "aft starboard pod frame" + icon_state = "pod_as" + desc = "A space pod frame component. This is the aft starboard component." + link_to = /obj/item/pod_parts/pod_frame/aft_port + link_angle = 270 + +/obj/item/pod_parts/armor + name = "civilian pod armor" + icon_state = "pod_armor_civ" + desc = "Spacepod armor. This is the civilian version. It looks rather flimsy." + var/pod_icon = 'yogstation/goon/icons/obj/spacepods/2x2.dmi' + var/pod_icon_state = "pod_civ" + var/pod_desc = "A sleek civilian space pod." + var/pod_integrity = 250 + +/obj/item/pod_parts/armor/syndicate + name = "syndicate pod armor" + icon_state = "pod_armor_synd" + desc = "Tough-looking spacepod armor, with a bold \"FUCK NT\" stenciled directly into it." + pod_icon_state = "pod_synd" + pod_desc = "A menacing military space pod with \"FUCK NT\" stenciled onto the side" + pod_integrity = 400 + +/obj/item/pod_parts/armor/black + name = "black pod armor" + icon_state = "pod_armor_black" + desc = "Plain black spacepod armor, with no logos or insignias anywhere on it." + pod_icon_state = "pod_black" + pod_desc = "An all black space pod with no insignias." + +/obj/item/pod_parts/armor/gold + name = "golden pod armor" + icon_state = "pod_armor_gold" + desc = "Golden spacepod armor. Looks like what a rich spessmen put on their spacepod." + pod_icon_state = "pod_gold" + pod_desc = "A civilian space pod with a gold body, must have cost somebody a pretty penny" + pod_integrity = 220 + +/obj/item/pod_parts/armor/industrial + name = "industrial pod armor" + icon_state = "pod_armor_industrial" + desc = "Tough industrial-grade spacepod armor. While meant for construction work, it is commonly used in spacepod battles, too." + pod_icon_state = "pod_industrial" + pod_desc = "A rough looking space pod meant for industrial work" + pod_integrity = 330 + +/obj/item/pod_parts/armor/security + name = "security pod armor" + icon_state = "pod_armor_mil" + desc = "Tough military-grade pod armor, meant for use by the NanoTrasen military and it's sub-divisons for space combat." + pod_icon_state = "pod_mil" + pod_desc = "An armed security spacepod with reinforced armor plating brandishing the Nanotrasen Military insignia" + pod_integrity = 350 + +/obj/item/circuitboard/mecha/pod + name = "Circuit board (Space Pod Mainboard)" + icon_state = "mainboard" diff --git a/yogstation/code/modules/spacepods/physics.dm b/yogstation/code/modules/spacepods/physics.dm new file mode 100644 index 000000000000..3bf8824b27c9 --- /dev/null +++ b/yogstation/code/modules/spacepods/physics.dm @@ -0,0 +1,295 @@ +/obj/spacepod/process(time) + time /= 10 // fuck off with your deciseconds + + if(world.time > last_slowprocess + 15) + last_slowprocess = world.time + slowprocess() + + var/last_offset_x = offset_x + var/last_offset_y = offset_y + var/last_angle = angle + var/desired_angular_velocity = 0 + if(isnum(desired_angle)) + // do some finagling to make sure that our angles end up rotating the short way + while(angle > desired_angle + 180) + angle -= 360 + last_angle -= 360 + while(angle < desired_angle - 180) + angle += 360 + last_angle += 360 + if(abs(desired_angle - angle) < (max_angular_acceleration * time)) + desired_angular_velocity = (desired_angle - angle) / time + else if(desired_angle > angle) + desired_angular_velocity = 2 * sqrt((desired_angle - angle) * max_angular_acceleration * 0.25) + else + desired_angular_velocity = -2 * sqrt((angle - desired_angle) * max_angular_acceleration * 0.25) + var/angular_velocity_adjustment = CLAMP(desired_angular_velocity - angular_velocity, -max_angular_acceleration*time, max_angular_acceleration*time) + if(angular_velocity_adjustment && cell && cell.use(abs(angular_velocity_adjustment) * 0.05)) + last_rotate = angular_velocity_adjustment / time + angular_velocity += angular_velocity_adjustment + else + last_rotate = 0 + angle += angular_velocity * time + + // calculate drag and shit + + var/velocity_mag = sqrt(velocity_x*velocity_x+velocity_y*velocity_y) // magnitude + if(velocity_mag || angular_velocity != 0) + var/drag = 0 + for(var/turf/T in locs) + if(isspaceturf(T)) + continue + drag += 0.001 + var/floating = FALSE + if(T.has_gravity() && !brakes && velocity_mag > 0.1 && cell && cell.use((is_mining_level(z) ? 3 : 15) * time)) + floating = TRUE // want to fly this shit on the station? Have fun draining your battery. + if((!floating && T.has_gravity()) || brakes) // brakes are a kind of magboots okay? + drag += is_mining_level(z) ? 0.1 : 0.5 // some serious drag. Damn. Except lavaland, it has less gravity or something + if(velocity_mag > 5 && prob(velocity_mag * 4) && istype(T, /turf/open/floor)) + var/turf/open/floor/TF = T + TF.make_plating() // pull up some floor tiles. Stop going so fast, ree. + var/datum/gas_mixture/env = T.return_air() + if(env) + var/pressure = env.return_pressure() + drag += velocity_mag * pressure * 0.0001 // 1 atmosphere should shave off 1% of velocity per tile + if(velocity_mag > 20) + drag = max(drag, (velocity_mag - 20) / time) + if(drag) + if(velocity_mag) + var/drag_factor = 1 - CLAMP(drag * time / velocity_mag, 0, 1) + velocity_x *= drag_factor + velocity_y *= drag_factor + if(angular_velocity != 0) + var/drag_factor_spin = 1 - CLAMP(drag * 30 * time / abs(angular_velocity), 0, 1) + angular_velocity *= drag_factor_spin + + // Alright now calculate the THRUST + var/thrust_x + var/thrust_y + var/fx = cos(90 - angle) + var/fy = sin(90 - angle) + var/sx = fy + var/sy = -fx + last_thrust_forward = 0 + last_thrust_right = 0 + if(brakes) + if(user_thrust_dir) + to_chat(pilot, "Your brakes are on!") + // basically calculates how much we can brake using the thrust + var/forward_thrust = -((fx * velocity_x) + (fy * velocity_y)) / time + var/right_thrust = -((sx * velocity_x) + (sy * velocity_y)) / time + forward_thrust = CLAMP(forward_thrust, -backward_maxthrust, forward_maxthrust) + right_thrust = CLAMP(right_thrust, -side_maxthrust, side_maxthrust) + thrust_x += forward_thrust * fx + right_thrust * sx; + thrust_y += forward_thrust * fy + right_thrust * sy; + last_thrust_forward = forward_thrust + last_thrust_right = right_thrust + else // want some sort of help piloting the ship? Haha no fuck you do it yourself + if(user_thrust_dir & NORTH) + thrust_x += fx * forward_maxthrust + thrust_y += fy * forward_maxthrust + last_thrust_forward = forward_maxthrust + if(user_thrust_dir & SOUTH) + thrust_x -= fx * backward_maxthrust + thrust_y -= fy * backward_maxthrust + last_thrust_forward = -backward_maxthrust + if(user_thrust_dir & EAST) + thrust_x += sx * side_maxthrust + thrust_y += sy * side_maxthrust + last_thrust_right = side_maxthrust + if(user_thrust_dir & WEST) + thrust_x -= sx * side_maxthrust + thrust_y -= sy * side_maxthrust + last_thrust_right = -side_maxthrust + + if(cell && cell.use(10 * sqrt((thrust_x*thrust_x)+(thrust_y*thrust_y)) * time)) + velocity_x += thrust_x * time + velocity_y += thrust_y * time + else + last_thrust_forward = 0 + last_thrust_right = 0 + if(!brakes && user_thrust_dir) + to_chat(pilot, "You are out of power!") + + offset_x += velocity_x * time + offset_y += velocity_y * time + // alright so now we reconcile the offsets with the in-world position. + while((offset_x > 0 && velocity_x > 0) || (offset_y > 0 && velocity_y > 0) || (offset_x < 0 && velocity_x < 0) || (offset_y < 0 && velocity_y < 0)) + var/failed_x = FALSE + var/failed_y = FALSE + if(offset_x > 0 && velocity_x > 0) + dir = EAST + if(!Move(get_step(src, EAST))) + offset_x = 0 + failed_x = TRUE + velocity_x *= -bounce_factor + velocity_y *= lateral_bounce_factor + else + offset_x-- + last_offset_x-- + else if(offset_x < 0 && velocity_x < 0) + dir = WEST + if(!Move(get_step(src, WEST))) + offset_x = 0 + failed_x = TRUE + velocity_x *= -bounce_factor + velocity_y *= lateral_bounce_factor + else + offset_x++ + last_offset_x++ + else + failed_x = TRUE + if(offset_y > 0 && velocity_y > 0) + dir = NORTH + if(!Move(get_step(src, NORTH))) + offset_y = 0 + failed_y = TRUE + velocity_y *= -bounce_factor + velocity_x *= lateral_bounce_factor + else + offset_y-- + last_offset_y-- + else if(offset_y < 0 && velocity_y < 0) + dir = SOUTH + if(!Move(get_step(src, SOUTH))) + offset_y = 0 + failed_y = TRUE + velocity_y *= -bounce_factor + velocity_x *= lateral_bounce_factor + else + offset_y++ + last_offset_y++ + else + failed_y = TRUE + if(failed_x && failed_y) + break + // prevents situations where you go "wtf I'm clearly right next to it" as you enter a stationary spacepod + if(velocity_x == 0) + if(offset_x > 0.5) + if(Move(get_step(src, EAST))) + offset_x-- + last_offset_x-- + else + offset_x = 0 + if(offset_x < -0.5) + if(Move(get_step(src, WEST))) + offset_x++ + last_offset_x++ + else + offset_x = 0 + if(velocity_y == 0) + if(offset_y > 0.5) + if(Move(get_step(src, NORTH))) + offset_y-- + last_offset_y-- + else + offset_y = 0 + if(offset_y < -0.5) + if(Move(get_step(src, SOUTH))) + offset_y++ + last_offset_y++ + else + offset_y = 0 + dir = NORTH + var/matrix/mat_from = new() + mat_from.Turn(last_angle) + var/matrix/mat_to = new() + mat_to.Turn(angle) + transform = mat_from + pixel_x = last_offset_x*32 + pixel_y = last_offset_y*32 + animate(src, transform=mat_to, pixel_x = offset_x*32, pixel_y = offset_y*32, time = time*10, flags=ANIMATION_END_NOW) + for(var/mob/living/M in contents) + var/client/C = M.client + if(!C) + continue + C.pixel_x = last_offset_x*32 + C.pixel_y = last_offset_y*32 + animate(C, pixel_x = offset_x*32, pixel_y = offset_y*32, time = time*10, flags=ANIMATION_END_NOW) + user_thrust_dir = 0 + update_icon() + +/obj/spacepod/Bumped(atom/movable/A) + if(A.dir & NORTH) + velocity_y += bump_impulse + if(A.dir & SOUTH) + velocity_y -= bump_impulse + if(A.dir & EAST) + velocity_x += bump_impulse + if(A.dir & WEST) + velocity_x -= bump_impulse + return ..() + +/obj/spacepod/Bump(atom/A) + var/bump_velocity = 0 + if(dir & (NORTH|SOUTH)) + bump_velocity = abs(velocity_y) + (abs(velocity_x) / 15) + else + bump_velocity = abs(velocity_x) + (abs(velocity_y) / 15) + if(istype(A, /obj/machinery/door/airlock)) // try to open doors + var/obj/machinery/door/D = A + if(!D.operating) + if(D.allowed(D.requiresID() ? pilot : null)) + spawn(0) + D.open() + else + D.do_animate("deny") + var/atom/movable/AM = A + if(istype(AM) && !AM.anchored && bump_velocity > 1) + step(AM, dir) + // if a bump is that fast then it's not a bump. It's a collision. + if(bump_velocity > 10 && !ismob(A)) + var/strength = bump_velocity / 10 + strength = strength * strength + strength = min(strength, 5) // don't want the explosions *too* big + // wew lad, might wanna slow down there + explosion(A, -1, round((strength - 1) / 2), round(strength)) + message_admins("[key_name_admin(pilot)] has impacted a spacepod into a wall with velocity [bump_velocity]") + take_damage(strength*10, BRUTE, "melee", TRUE) + log_game("[key_name(pilot)] has impacted a spacepod into a wall with velocity [bump_velocity]") + visible_message("The force of the impact causes a shockwave") + else if(isliving(A) && bump_velocity > 5) + var/mob/living/M = A + M.apply_damage(bump_velocity * 2) + take_damage(bump_velocity, BRUTE, "melee", FALSE) + playsound(M.loc, "swing_hit", 1000, 1, -1) + M.Knockdown(bump_velocity * 2) + M.visible_message("The force of the impact knocks [M] down!","The force of the impact knocks you down!") + log_combat(pilot, M, "impacted", src, "with velocity of [bump_velocity]") + return ..() + +/obj/spacepod/proc/fire_projectiles(proj_type, target) // if spacepods of other sizes are added override this or something + var/fx = cos(90 - angle) + var/fy = sin(90 - angle) + var/sx = fy + var/sy = -fx + var/ox = (offset_x * 32) + 16 + var/oy = (offset_y * 32) + 16 + var/list/origins = list(list(ox + fx*16 - sx*16, oy + fy*16 - sy*16), list(ox + fx*16 + sx*16, oy + fy*16 + sy*16)) + for(var/list/origin in origins) + var/this_x = origin[1] + var/this_y = origin[2] + var/turf/T = get_turf(src) + while(this_x > 16) + T = get_step(T, EAST) + this_x -= 32 + while(this_x < -16) + T = get_step(T, WEST) + this_x += 32 + while(this_y > 16) + T = get_step(T, NORTH) + this_y -= 32 + while(this_y < -16) + T = get_step(T, SOUTH) + this_y += 32 + if(!T) + continue + var/obj/item/projectile/proj = new proj_type(T) + proj.starting = T + proj.firer = usr + proj.def_zone = "chest" + proj.original = target + proj.pixel_x = round(this_x) + proj.pixel_y = round(this_y) + spawn() + proj.fire(angle) diff --git a/yogstation/code/modules/spacepods/prebuilt.dm b/yogstation/code/modules/spacepods/prebuilt.dm new file mode 100644 index 000000000000..5414cded2865 --- /dev/null +++ b/yogstation/code/modules/spacepods/prebuilt.dm @@ -0,0 +1,29 @@ +/obj/spacepod/prebuilt + icon = 'yogstation/goon/icons/obj/spacepods/2x2.dmi' + icon_state = "pod_civ" + var/cell_type = /obj/item/stock_parts/cell/high/plus + var/armor_type = /obj/item/pod_parts/armor + var/internal_tank_type = /obj/machinery/portable_atmospherics/canister/air + var/equipment_types = list() + construction_state = SPACEPOD_ARMOR_WELDED + +/obj/spacepod/prebuilt/Initialize() + ..() + add_armor(new armor_type(src)) + if(cell_type) + cell = new cell_type(src) + if(internal_tank_type) + internal_tank = new internal_tank_type(src) + for(var/equip in equipment_types) + var/obj/item/spacepod_equipment/SE = new equip(src) + SE.on_install(src) + +/obj/spacepod/prebuilt/sec + name = "security space pod" + icon_state = "pod_mil" + locked = TRUE + armor_type = /obj/item/pod_parts/armor/security + equipment_types = list(/obj/item/spacepod_equipment/weaponry/disabler, + /obj/item/spacepod_equipment/lock/keyed/sec, + /obj/item/spacepod_equipment/tracker, + /obj/item/spacepod_equipment/cargo/chair) diff --git a/yogstation/code/modules/spacepods/spacepod.dm b/yogstation/code/modules/spacepods/spacepod.dm new file mode 100644 index 000000000000..91fcc51a2358 --- /dev/null +++ b/yogstation/code/modules/spacepods/spacepod.dm @@ -0,0 +1,683 @@ +// This is like paradise spacepods but with a few differences: +// - no spacepod fabricator, parts are made in techfabs and frames are made using metal rods. +// - not tile based, instead has velocity and acceleration. why? so I can put all this math to use. +// - damages shit if you run into it too fast instead of just stopping. You have to have a huge running start to do that though and damages the spacepod as well. +// - doesn't explode + +GLOBAL_LIST_INIT(spacepods_list, list()) + +/obj/spacepod + name = "space pod" + desc = "A frame for a spacepod." + icon = 'yogstation/goon/icons/obj/spacepods/construction_2x2.dmi' + icon_state = "pod_1" + density = 1 + opacity = 0 + dir = NORTH // always points north because why not + layer = SPACEPOD_LAYER + bound_width = 64 + bound_height = 64 + animate_movement = NO_STEPS // we do our own gliding here + + anchored = 1 + resistance_flags = LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF // it floats above lava or something, I dunno + + max_integrity = 50 + integrity_failure = 50 + + var/list/equipment = list() + var/list/equipment_slot_limits = list( + SPACEPOD_SLOT_MISC = 1, + SPACEPOD_SLOT_CARGO = 2, + SPACEPOD_SLOT_WEAPON = 1, + SPACEPOD_SLOT_LOCK = 1) + var/obj/item/spacepod_equipment/lock/lock + var/obj/item/spacepod_equipment/weaponry/weapon + var/next_firetime = 0 + var/locked = FALSE + var/hatch_open = FALSE + var/construction_state = SPACEPOD_EMPTY + var/obj/item/pod_parts/armor/pod_armor = null + var/obj/item/stock_parts/cell/cell = null + var/datum/gas_mixture/cabin_air + var/obj/machinery/portable_atmospherics/canister/internal_tank + var/last_slowprocess = 0 + + var/mob/living/pilot + var/list/passengers = list() + var/max_passengers = 0 + + var/velocity_x = 0 // tiles per second. + var/velocity_y = 0 + var/offset_x = 0 // like pixel_x/y but in tiles + var/offset_y = 0 + var/angle = 0 // degrees, clockwise + var/desired_angle = null // set by pilot moving his mouse + var/angular_velocity = 0 // degrees per second + var/max_angular_acceleration = 360 // in degrees per second per second + var/last_thrust_forward = 0 + var/last_thrust_right = 0 + var/last_rotate = 0 + + var/brakes = TRUE + var/user_thrust_dir = 0 + var/forward_maxthrust = 6 + var/backward_maxthrust = 3 + var/side_maxthrust = 1 + + var/lights = 0 + var/lights_power = 6 + var/static/list/icon_light_color = list("pod_civ" = LIGHT_COLOR_WHITE, \ + "pod_mil" = "#BBF093", \ + "pod_synd" = LIGHT_COLOR_RED, \ + "pod_gold" = LIGHT_COLOR_WHITE, \ + "pod_black" = "#3B8FE5", \ + "pod_industrial" = "#CCCC00") + + var/bump_impulse = 0.6 + var/bounce_factor = 0.2 // how much of our velocity to keep on collision + var/lateral_bounce_factor = 0.95 // mostly there to slow you down when you drive (pilot?) down a 2x2 corridor + +/obj/spacepod/Initialize() + . = ..() + GLOB.spacepods_list += src + START_PROCESSING(SSfastprocess, src) + cabin_air = new + cabin_air.temperature = T20C + cabin_air.volume = 200 + /*cabin_air.assert_gas(/datum/gas/oxygen) + cabin_air.assert_gas(/datum/gas/nitrogen) + cabin_air.gases[/datum/gas/oxygen][MOLES] = ONE_ATMOSPHERE*O2STANDARD*cabin_air.volume/(R_IDEAL_GAS_EQUATION*cabin_air.temperature) + cabin_air.gases[/datum/gas/nitrogen][MOLES] = ONE_ATMOSPHERE*N2STANDARD*cabin_air.volume/(R_IDEAL_GAS_EQUATION*cabin_air.temperature)*/ + +/obj/spacepod/Destroy() + GLOB.spacepods_list -= src + QDEL_NULL(pilot) + QDEL_LIST(passengers) + QDEL_LIST(equipment) + QDEL_NULL(cabin_air) + QDEL_NULL(cell) + return ..() + +/obj/spacepod/attackby(obj/item/W, mob/living/user) + if(user.a_intent == INTENT_HARM) + return ..() + else if(construction_state != SPACEPOD_ARMOR_WELDED) + . = handle_spacepod_construction(W, user) + if(.) + return + else + return ..() + // and now for the real stuff + else + if(W.tool_behaviour == TOOL_CROWBAR) + if(hatch_open || !locked) + hatch_open = !hatch_open + W.play_tool_sound(src) + to_chat(user, "You [hatch_open ? "open" : "close"] the maintenance hatch.") + else + to_chat(user, "The hatch is locked shut!") + return TRUE + if(istype(W, /obj/item/stock_parts/cell)) + if(!hatch_open) + to_chat(user, "The maintenance hatch is closed!") + return TRUE + if(cell) + to_chat(user, "The pod already has a battery.") + return TRUE + if(user.transferItemToLoc(W, src)) + to_chat(user, "You insert [W] into the pod.") + cell = W + return TRUE + if(istype(W, /obj/item/spacepod_equipment)) + if(!hatch_open) + to_chat(user, "The maintenance hatch is closed!") + return TRUE + var/obj/item/spacepod_equipment/SE = W + if(SE.can_install(src, user) && user.temporarilyRemoveItemFromInventory(SE)) + SE.forceMove(src) + SE.on_install(src) + return TRUE + if(lock && istype(W, /obj/item/device/lock_buster)) + var/obj/item/device/lock_buster/L = W + if(L.on) + user.visible_message(user, "[user] is drilling through the [src]'s lock!", + "You start drilling through the [src]'s lock!") + if(do_after(user, 100 * W.toolspeed, target = src)) + if(lock) + var/obj/O = lock + lock.on_uninstall() + qdel(O) + user.visible_message(user, "[user] has destroyed the [src]'s lock!", + "You destroy the [src]'s lock!") + else + user.visible_message(user, "[user] fails to break through the [src]'s lock!", + "You were unable to break through the [src]'s lock!") + return TRUE + to_chat(user, "Turn the [L] on first.") + return TRUE + if(W.tool_behaviour == TOOL_WELDER) + var/repairing = cell || internal_tank || equipment.len || (obj_integrity < max_integrity) || pilot || passengers.len + if(!hatch_open) + to_chat(user, "You must open the maintenance hatch before [repairing ? "attempting repairs" : "unwelding the armor"].") + return TRUE + if(repairing && obj_integrity >= max_integrity) + to_chat(user, "[src] is fully repaired!") + return TRUE + to_chat(user, "You start [repairing ? "repairing [src]" : "slicing off [src]'s armor'"]") + if(W.use_tool(src, user, 50, amount=3, volume = 50)) + if(repairing) + obj_integrity = min(max_integrity, obj_integrity + 10) + update_icon() + to_chat(user, "You mend some [pick("dents","bumps","damage")] with [W]") + else if(!cell && !internal_tank && !equipment.len && !pilot && !passengers.len && construction_state == SPACEPOD_ARMOR_WELDED) + user.visible_message("[user] slices off [src]'s armor.", "You slice off [src]'s armor.") + construction_state = SPACEPOD_ARMOR_SECURED + update_icon() + return ..() + +/obj/spacepod/attack_hand(mob/user as mob) + if(user.a_intent == INTENT_GRAB && !locked) + var/mob/living/target + if(pilot) + target = pilot + else if(passengers.len > 0) + target = passengers[1] + + if(target && istype(target)) + src.visible_message("[user] is trying to rip the door open and pull [target] out of the [src]!", + "You see [user] outside the door trying to rip it open!") + if(do_after(user, 50, target = src) && construction_state == SPACEPOD_ARMOR_WELDED) + if(remove_rider(target)) + target.Stun(20) + target.visible_message("[user] flings the door open and tears [target] out of the [src]", + "The door flies open and you are thrown out of the [src] and to the ground!") + return + target.visible_message("[user] was unable to get the door open!", + "You manage to keep [user] out of the [src]!") + + if(!hatch_open) + //if(cargo_hold.storage_slots > 0) + // if(!locked) + // cargo_hold.open(user) + // else + // to_chat(user, "The storage compartment is locked") + return ..() + var/list/items = list(cell, internal_tank) + items += equipment + var/list/item_map = list() + for(var/obj/I in items) + item_map[I.name] = I + var/selection = input(user, "Remove which equipment?", null, null) as null|anything in item_map + var/obj/O = item_map[selection] + if(O && istype(O) && O in contents) + // alrightey now to figure out what it is + if(O == cell) + cell = null + else if(O == internal_tank) + internal_tank = null + else if(O in equipment) + var/obj/item/spacepod_equipment/SE = O + if(!SE.can_uninstall(user)) + return + SE.on_uninstall() + else + return + O.forceMove(loc) + if(isitem(O)) + user.put_in_hands(O) + + +/obj/spacepod/proc/add_armor(obj/item/pod_parts/armor/armor) + desc = armor.pod_desc + max_integrity = armor.pod_integrity + obj_integrity = max_integrity - integrity_failure + obj_integrity + pod_armor = armor + update_icon() + +/obj/spacepod/proc/remove_armor() + if(!pod_armor) + obj_integrity = min(integrity_failure, obj_integrity) + max_integrity = integrity_failure + desc = initial(desc) + pod_armor = null + update_icon() + + +/obj/spacepod/proc/InterceptClickOn(mob/user, params, atom/target) + var/list/params_list = params2list(params) + if(target == src || istype(target, /obj/screen) || (target && (target in user.GetAllContents())) || user != pilot || params_list["shift"] || params_list["alt"] || params_list["ctrl"]) + return FALSE + if(weapon) + weapon.fire_weapons(target) + return TRUE + +/obj/spacepod/take_damage() + ..() + update_icon() + +/obj/spacepod/return_air() + return cabin_air +/obj/spacepod/remove_air(amount) + return cabin_air.remove(amount) + +/obj/spacepod/proc/slowprocess() + if(cabin_air && cabin_air.volume > 0) + var/delta = cabin_air.temperature - T20C + cabin_air.temperature -= max(-10, min(10, round(delta/4,0.1))) + if(internal_tank && cabin_air) + var/datum/gas_mixture/tank_air = internal_tank.return_air() + + var/release_pressure = ONE_ATMOSPHERE + var/cabin_pressure = cabin_air.return_pressure() + var/pressure_delta = min(release_pressure - cabin_pressure, (tank_air.return_pressure() - cabin_pressure)/2) + var/transfer_moles = 0 + if(pressure_delta > 0) //cabin pressure lower than release pressure + if(tank_air.return_temperature() > 0) + transfer_moles = pressure_delta*cabin_air.return_volume()/(cabin_air.return_temperature() * R_IDEAL_GAS_EQUATION) + var/datum/gas_mixture/removed = tank_air.remove(transfer_moles) + cabin_air.merge(removed) + else if(pressure_delta < 0) //cabin pressure higher than release pressure + var/turf/T = get_turf(src) + var/datum/gas_mixture/t_air = T.return_air() + pressure_delta = cabin_pressure - release_pressure + if(t_air) + pressure_delta = min(cabin_pressure - t_air.return_pressure(), pressure_delta) + if(pressure_delta > 0) //if location pressure is lower than cabin pressure + transfer_moles = pressure_delta*cabin_air.return_volume()/(cabin_air.return_temperature() * R_IDEAL_GAS_EQUATION) + var/datum/gas_mixture/removed = cabin_air.remove(transfer_moles) + if(T) + T.assume_air(removed) + else //just delete the cabin gas, we're in space or some shit + qdel(removed) + +/mob/Stat() + . = ..() + if(isspacepod(loc) && statpanel("Status")) + var/obj/spacepod/S = loc + stat(null) + stat(null, "Spacepod Charge: [S.cell ? "[round(S.cell.charge,0.1)]/[S.cell.maxcharge] KJ" : "NONE"]") + stat(null, "Spacepod Integrity: [round(S.obj_integrity,0.1)]/[S.max_integrity]") + stat(null, "Spacepod Velocity: [round(sqrt(S.velocity_x*S.velocity_x+S.velocity_y*S.velocity_y), 0.1)] m/s") + stat(null) + +/obj/spacepod/ex_act(severity) + switch(severity) + if(1) + for(var/mob/living/M in contents) + M.ex_act(severity+1) + deconstruct() + if(2) + take_damage(100, BRUTE, "bomb", 0) + if(3) + if(prob(40)) + take_damage(40, BRUTE, "bomb", 0) + +/obj/spacepod/obj_break() + if(obj_integrity <= 0) + return // nah we'll let the other boy handle it + if(construction_state < SPACEPOD_ARMOR_LOOSE) + return + if(pod_armor) + var/obj/A = pod_armor + remove_armor() + qdel(A) + if(prob(40)) + new /obj/item/stack/sheet/metal/five(loc) + if(prob(40)) + new /obj/item/stack/sheet/metal/five(loc) + construction_state = SPACEPOD_CORE_SECURED + if(cabin_air) + var/datum/gas_mixture/GM = cabin_air.remove_ratio(1) + var/turf/T = get_turf(src) + if(GM && T) + T.assume_air(GM) + cell = null + internal_tank = null + for(var/atom/movable/AM in contents) + if(ismob(AM)) + forceMove(AM, loc) + remove_rider(AM) + else if(prob(60)) + AM.forceMove(loc) + else if(isitem(AM) || !isobj(AM)) + qdel(AM) + else + var/obj/O = AM + O.forceMove(loc) + O.deconstruct() + + +/obj/spacepod/deconstruct(disassembled = FALSE) + if(!get_turf(src)) + qdel(src) + return + remove_rider(pilot) + while(passengers.len) + remove_rider(passengers[1]) + passengers.Cut() + if(disassembled) + // AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + // alright fine fine you can have the frame pieces back + var/clamped_angle = (round(angle, 90) % 360 + 360) % 360 + var/target_dir = NORTH + switch(clamped_angle) + if(0) + target_dir = NORTH + if(90) + target_dir = EAST + if(180) + target_dir = SOUTH + if(270) + target_dir = WEST + + var/list/frame_piece_types = list(/obj/item/pod_parts/pod_frame/aft_port, /obj/item/pod_parts/pod_frame/aft_starboard, /obj/item/pod_parts/pod_frame/fore_port, /obj/item/pod_parts/pod_frame/fore_starboard) + var/obj/item/pod_parts/pod_frame/current_piece = null + var/turf/CT = get_turf(src) + var/list/frame_pieces = list() + for(var/frame_type in frame_piece_types) + var/obj/item/pod_parts/pod_frame/F = new frame_type + F.dir = target_dir + F.anchored = TRUE + if(1 == turn(F.dir, -F.link_angle)) + current_piece = F + frame_pieces += F + while(current_piece && !current_piece.loc) + if(!CT) + break + current_piece.forceMove(CT) + CT = get_step(CT, turn(current_piece.dir, -current_piece.link_angle)) + current_piece = locate(current_piece.link_to) in frame_pieces + // there here's your frame pieces back, happy? + qdel(src) + +/obj/spacepod/update_icon() + cut_overlays() + if(construction_state != SPACEPOD_ARMOR_WELDED) + icon = 'yogstation/goon/icons/obj/spacepods/construction_2x2.dmi' + icon_state = "pod_[construction_state]" + if(pod_armor && construction_state >= SPACEPOD_ARMOR_LOOSE) + var/mutable_appearance/masked_armor = mutable_appearance(icon = 'yogstation/goon/icons/obj/spacepods/construction_2x2.dmi', icon_state = "armor_mask") + var/mutable_appearance/armor = mutable_appearance(pod_armor.pod_icon, pod_armor.pod_icon_state) + armor.blend_mode = BLEND_MULTIPLY + masked_armor.overlays = list(armor) + masked_armor.appearance_flags = KEEP_TOGETHER + add_overlay(masked_armor) + return + + if(pod_armor) + icon = pod_armor.pod_icon + icon_state = pod_armor.pod_icon_state + else + icon = 'yogstation/goon/icons/obj/spacepods/2x2.dmi' + icon_state = initial(icon_state) + + if(obj_integrity <= max_integrity / 2) + add_overlay(image(icon='yogstation/goon/icons/obj/spacepods/2x2.dmi', icon_state="pod_damage")) + if(obj_integrity <= max_integrity / 4) + add_overlay(image(icon='yogstation/goon/icons/obj/spacepods/2x2.dmi', icon_state="pod_fire")) + + if(weapon && weapon.overlay_icon_state) + add_overlay(image(icon=weapon.overlay_icon,icon_state=weapon.overlay_icon_state)) + + light_color = icon_light_color[icon_state] || LIGHT_COLOR_WHITE + + // Thrust! + var/list/left_thrusts = list() + left_thrusts.len = 8 + var/list/right_thrusts = list() + right_thrusts.len = 8 + for(var/cdir in GLOB.cardinals) + left_thrusts[cdir] = 0 + right_thrusts[cdir] = 0 + var/back_thrust = 0 + if(last_thrust_right != 0) + var/tdir = last_thrust_right > 0 ? WEST : EAST + left_thrusts[tdir] = abs(last_thrust_right) / side_maxthrust + right_thrusts[tdir] = abs(last_thrust_right) / side_maxthrust + if(last_thrust_forward > 0) + back_thrust = last_thrust_forward / forward_maxthrust + if(last_thrust_forward < 0) + left_thrusts[NORTH] = -last_thrust_forward / backward_maxthrust + right_thrusts[NORTH] = -last_thrust_forward / backward_maxthrust + if(last_rotate != 0) + var/frac = abs(last_rotate) / max_angular_acceleration + for(var/cdir in GLOB.cardinals) + if(last_rotate > 0) + right_thrusts[cdir] += frac + else + left_thrusts[cdir] += frac + for(var/cdir in GLOB.cardinals) + var/left_thrust = left_thrusts[cdir] + var/right_thrust = right_thrusts[cdir] + if(left_thrust) + add_overlay(image(icon = 'yogstation/icons/obj/spacepods/2x2.dmi', icon_state = "rcs_left", dir = cdir)) + if(right_thrust) + add_overlay(image(icon = 'yogstation/icons/obj/spacepods/2x2.dmi', icon_state = "rcs_right", dir = cdir)) + if(back_thrust) + var/image/I = image(icon = 'yogstation/icons/obj/spacepods/2x2.dmi', icon_state = "thrust") + I.transform = matrix(1, 0, 0, 0, 1, -32) + add_overlay(I) + +/obj/spacepod/MouseDrop_T(atom/movable/A, mob/living/user) + if(user == pilot || user in passengers || construction_state != SPACEPOD_ARMOR_WELDED) + return + + if(istype(A, /obj/machinery/portable_atmospherics/canister)) + if(internal_tank) + to_chat(user, "[src] already has an internal_tank!") + return + if(!A.Adjacent(src)) + to_chat(user, "The canister is not close enough!") + return + if(hatch_open) + to_chat(user, "The hatch is shut!") + to_chat(user, "You begin inserting the canister into [src]") + if(do_after_mob(user, list(A, src), 50) && construction_state == SPACEPOD_ARMOR_WELDED) + to_chat(user, "You insert the canister into [src]") + A.forceMove(src) + internal_tank = A + return + + if(isliving(A)) + var/mob/living/M = A + if(M != user && !locked) + if(passengers.len >= max_passengers && !pilot) + to_chat(user, "That person can't fly the pod!") + return + if(passengers.len < max_passengers) + visible_message("[user] starts loading [M] into [src]!") + if(do_after_mob(user, list(M, src), 50) && construction_state == SPACEPOD_ARMOR_WELDED) + add_rider(M, FALSE) + return + if(M == user) + enter_pod(user) + return + + return ..() + +/obj/spacepod/proc/enter_pod(mob/living/user) + if(user.stat != CONSCIOUS) + return FALSE + + if(locked) + to_chat(user, "[src]'s doors are locked!") + return FALSE + + if(!istype(user)) + return FALSE + + if(user.incapacitated()) + return FALSE + if(!ishuman(user)) + return FALSE + + if(passengers.len <= max_passengers || !pilot) + visible_message("[user] starts to climb into [src].") + if(do_after(user, 40, target = src) && construction_state == SPACEPOD_ARMOR_WELDED) + var/success = add_rider(user) + if(!success) + to_chat(user, "You were too slow. Try better next time, loser.") + return success + else + to_chat(user, "You stop entering [src].") + else + to_chat(user, "You can't fit in [src], it's full!") + return FALSE + +/obj/spacepod/proc/verb_check(require_pilot = TRUE, mob/user = null) + if(!user) + user = usr + if(require_pilot && user != pilot) + to_chat(user, "You can't reach the controls from your chair") + return FALSE + return !user.incapacitated() && isliving(user) + +/obj/spacepod/verb/exit_pod() + set name = "Exit pod" + set category = "Spacepod" + set src = usr.loc + + if(!isliving(usr) || usr.stat > CONSCIOUS) + return + + if(usr.restrained()) + to_chat(usr, "You attempt to stumble out of the [src]. This will take two minutes.") + if(pilot) + to_chat(pilot, "[usr] is trying to escape the [src].") + if(!do_after(usr, 1200, target = src)) + return + + if(remove_rider(usr)) + to_chat(usr, "You climb out of [src].") + +/obj/spacepod/verb/lock_pod() + set name = "Lock Doors" + set category = "Spacepod" + set src = usr.loc + + if(!verb_check(FALSE)) + return + + if(!lock) + to_chat(usr, "[src] has no locking mechanism.") + locked = FALSE //Should never be false without a lock, but if it somehow happens, that will force an unlock. + else + locked = !locked + to_chat(usr, "You [locked ? "lock" : "unlock"] the doors.") + +/obj/spacepod/verb/toggle_brakes() + set name = "Toggle Brakes" + set category = "Spacepod" + set src = usr.loc + + if(!verb_check()) + return + brakes = !brakes + to_chat(usr, "You toggle the brakes [brakes ? "on" : "off"].") + +/obj/spacepod/AltClick(user) + if(!verb_check(user = user)) + return + brakes = !brakes + to_chat(usr, "You toggle the brakes [brakes ? "on" : "off"].") + +/obj/spacepod/verb/toggleLights() + set name = "Toggle Lights" + set category = "Spacepod" + set src = usr.loc + + if(!verb_check()) + return + + lights = !lights + if(lights) + set_light(lights_power) + else + set_light(0) + to_chat(usr, "Lights toggled [lights ? "on" : "off"].") + for(var/mob/M in passengers) + to_chat(M, "Lights toggled [lights ? "on" : "off"].") + +/obj/spacepod/verb/toggleDoors() + set name = "Toggle Nearby Pod Doors" + set category = "Spacepod" + set src = usr.loc + + if(!verb_check()) + return + + for(var/obj/machinery/door/poddoor/multi_tile/P in orange(3,src)) + for(var/mob/living/carbon/human/O in contents) + if(P.check_access(O.get_active_held_item()) || P.check_access(O.wear_id)) + if(P.density) + P.open() + return TRUE + else + P.close() + return TRUE + to_chat(usr, "Access denied.") + return + + to_chat(usr, "You are not close to any pod doors.") + +/obj/spacepod/proc/add_rider(mob/living/M, allow_pilot = TRUE) + if(M == pilot || (M in passengers)) + return FALSE + if(!pilot && allow_pilot) + pilot = M + LAZYOR(M.mousemove_intercept_objects, src) + M.click_intercept = src + else if(passengers.len < max_passengers) + passengers += M + else + return FALSE + M.stop_pulling() + M.forceMove(src) + playsound(src, 'sound/machines/windowdoor.ogg', 50, 1) + return TRUE + +/obj/spacepod/proc/remove_rider(mob/living/M) + if(!M) + return + if(M == pilot) + pilot = null + LAZYREMOVE(M.mousemove_intercept_objects, src) + if(M.click_intercept == src) + M.click_intercept = null + desired_angle = null // since there's no pilot there's no one aiming it. + else if(M in passengers) + passengers -= M + else + return FALSE + if(M.loc == src) + M.forceMove(loc) + if(M.client) + M.client.pixel_x = 0 + M.client.pixel_y = 0 + return TRUE + +/obj/spacepod/onMouseMove(object,location,control,params) + if(!pilot || !pilot.client || pilot.incapacitated()) + return // I don't know what's going on. + var/list/params_list = params2list(params) + var/sl_list = splittext(params_list["screen-loc"],",") + var/sl_x_list = splittext(sl_list[1], ":") + var/sl_y_list = splittext(sl_list[2], ":") + var/view_list = isnum(pilot.client.view) ? list("[pilot.client.view*2+1]","[pilot.client.view*2+1]") : splittext(pilot.client.view, "x") + var/dx = text2num(sl_x_list[1]) + (text2num(sl_x_list[2]) / world.icon_size) - 1 - text2num(view_list[1]) / 2 + var/dy = text2num(sl_y_list[1]) + (text2num(sl_y_list[2]) / world.icon_size) - 1 - text2num(view_list[2]) / 2 + if(sqrt(dx*dx+dy*dy) > 1) + desired_angle = 90 - ATAN2(dx, dy) + else + desired_angle = null + +/obj/spacepod/relaymove(mob/user, direction) + if(user != pilot || pilot.incapacitated()) + return + user_thrust_dir = direction + +/obj/spacepod/Entered() + . = ..() +/obj/spacepod/Exited() + . = ..() diff --git a/yogstation/goon/LICENSE.md b/yogstation/goon/LICENSE.md new file mode 100644 index 000000000000..d227d11c6cdf --- /dev/null +++ b/yogstation/goon/LICENSE.md @@ -0,0 +1,4 @@ +This work is licensed under the Creative Commons +Attribution-NonCommercial-ShareAlike 3.0 United States License. To view a copy +of this license, visit http://creativecommons.org/licenses/by-nc-sa/3.0/us/ or +send a letter to Creative Commons, PO Box 1866, Mountain View, CA 94042, USA. \ No newline at end of file diff --git a/yogstation/goon/README.md b/yogstation/goon/README.md new file mode 100644 index 000000000000..cae34de90841 --- /dev/null +++ b/yogstation/goon/README.md @@ -0,0 +1,8 @@ +# Goon-ported-assets + +All files excluding this one you're reading right now have been most likely, taken from [goonstation's 2016 release](https://github.com/goonstation/goonstation-2016), unless stated otherwise. +It is very likely that there are modifications to be compatible or on par with our code, however. These changes are licensed under the same license as the other goon files. + +## License + +See LICENSE.md diff --git a/yogstation/goon/icons/obj/spacepods/2x2.dmi b/yogstation/goon/icons/obj/spacepods/2x2.dmi new file mode 100644 index 0000000000000000000000000000000000000000..02def41cf1bc43bf9a86c41adc898a1f16504cde GIT binary patch literal 7679 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 XjZR|H`S literal 0 HcmV?d00001 diff --git a/yogstation/icons/effects/beam.dmi b/yogstation/icons/effects/beam.dmi new file mode 100644 index 0000000000000000000000000000000000000000..792622d8fd2c2f99205f842540b66be795f4e83b GIT binary patch literal 1829 zcmaKteOOXg9LEoDf|`lVGM$K}+p3w_i!Yb~CR5C2CXiOXR^~HZ=_-U-;>|g9wnWWx zZEli=saVjOkF7|lW|*$T`8HDnCCx`uq^B3cuwAgdJbRw)kNZ34-h1x({?7M&&hLH| z5guY=nUx^Nb7j2!$F3pY?EGx`WE>G0k|pQIo8B<&yo+;W!Uveg$} zcnkHb>6N@At3?+V#vWW*ToM=`xSaGIl||3_tx>D2J=PVa>D)90x^_SG9=&=I|I=gZ zF1svC{084@p>A>8nxC&$;+d0vwYyinPh{|gN#CB@y&)()`Kcj;h`V%w5>xf=f~{$< zto>gQe_P39wE|#~5K0SVa?7WNE+23UvA?4`cN2-eipWqy4t}SWV?Bl|F=#1*t&Xw5 zd8HR`iICIVe1y(2O zTrr?wtQ6J19Jps}?^6Y(*`F`eh8vBI1#iLz1elmJN%tsm+Q#o9w~z=KqEgAnWaWD; zic#wiy-||B|7naFeCD$UL0AEg&JlY)kl8M^22-_ zMj(=yHplr4WoGkGX_*gkykuk}!$G)1cNXQkH5S_SOY%aH`V5h9k_>KjWAe#hpE)X_ zETZuGgdy_bsy6)%N?YdZ0hOv=QrVzIIj0G>O&T-S%lc+`J`!#Ac&b6*fhi(^TkGtp zv*>6z?XE*s3qVc@BGu zyApk0eb1WE@AR^~BqXD1HOX@KrM+|zy%)z^7Ub4Su*8>&>>d>^!`BxB=^gxv6=9}_ z5rH=rN(w6wL9Wqmuno&1FwgyE{yYnJYEdR3jzBpvs;|2hIo7~2+AD@6m9p!+CwY{P zsm8M-`a?0HTh8`BeEyECa9U8&QqHq(N##=mC)ix}YmX&jt$0ooU$-rc;gUVzVtmYw zip`d7YLeUzQGHOydOd8n{R@)OhvEFVP%8AWR2Py#{aS2|FfK1j(E*`xk&@eDP4Z1V zZhi^&=glv2&^5v)mv{mN#ivSxk#eUXp_N&00UdHRC%$CC@r09T7=a!kr%zzi+Xp;1 zxia0Y$`@V`^B(b{xl7ds0`AtheMh)UAiE*;N$^+8x3H+bx(tG$6Z| zlMjR38tVwL{#P>}^_^I1{x#5vh00A4tb%*qvt+rHGUx1*!g7}MGnlc^IBJ8JOGw?z zx-walz^J$z3C^PAyY&2TbXBLr^M)P?oKhBN%ivG=?hK-Um-fQ1Ieg45$Dxl$EYWxzr9_wZ$h*BhY!>5`qKJskKfn2 z8y#6KpK{5L4e&y*yF*AEwA+7jzO?-B#BU{y)&qB%f?XKMEAW30gnkrGyRWDy(!Vx=Vc-a;{f!7L!Q9%v4G~K_KDJ_Wt2P)Zm5t=Dv^pd(a>l5CHRZe~Jd6nY@RIfSw8T76i~t9*2-JlarZ% zMFixTJVgM{D%9A^0Os1U~#nj4p%2G!37B8I_SDfLeUOM3x|A3XuS8@Pd#^#QwEYi7{gSnz!~L z4xs=VEQwP3^FD9wzxCg4Fq#pV=dFE;0BEohDDH1WK)~Aou?5?|Hi6d=C?%lw$1`ch zOc-W>ux`W_Y=2%>L8qVNbwX)C_UDBycz(@5{x~l!P7u|P^WrXLK+sO09>)3n4sZkl zNO~m2S5@1L{gLjbDRYqkV_<9mPM@P~(#jmIE@J|j+Nh}Pt;(FF>}ld`bq_wVoo#

72kAR1Zx0#} z@bw@~z}tfj4GkRJD4| z1f)hV_Ja^F$-;!^B_Xb z`v+wI1Kd)w9%yK2XlQ6?XlQ6?Xz+r2jf*C*`&&xq8W&$9JAkYrV;90!f?JJ?mljb8 zORaG|7!VQU`yWAG-!}~E1JYyFL)P>E?E43NePHvE>i>B58qmvHTZO)iCVR@Cy~YIe zwN>cLU_$w`*NG4D1_wV8SwygWZ52KhFo|IK+A0Adr^4bKL5pnGHG(_==LpgSXa|rH z^tQj%=o%Ut8XESErp?j@kREhT=G@b1Zx8ZMuRDA2iSjkJygf*-u~k=A$#@5cetO-; zgXAh_(!=Q)YizM&&}Nl0kwC^8TXcN@8lXP^&%S?f1bN-r0D2~+*B4@z-?t#38mKg2 zzaLPQ0K0m9&i4ao0=U4SuP>z7uYNzEG68*kA!y#ZsZHSOSK~4w;8)|K2_*Q{xM%`w zGMDUe{C{wFeLZPOqEln4F4 zACTauZPEnN`TVp^BLaTfrVW8|57OTcs7yd#od%hg^Zfvt0O>*c`vFx6006)U0{{R3wB?vZ0000vP)t-sz`(#w zO-&gY8Md~zva+(8nwpc7lZ=dvg@uJ{Yis}iK>q+U%m66=nKR7HH4F?4Gcz-Pe}BNh zz+(a~IsgCw0d!JMQvg8b*k%9#0F8Q7Sad{Xb7OL8aCB*JZU6vyoKseCa&`CgQ*iP1 z*VtPKYPRbCFr^Gr7T#e zRI1{ZXVN@lu{(Y4+9+I*X44#9>W+a`C)(Gj&Z-=A7}6! z4Ss`#!ArEXFa(wE6VwO8Ux|Qr1auD;etM_|Nu!|RH^6M=u!5pnD9KpVdGlIYWB$fX8-4c906aIYG1pT|C-{8R! z^zSV`+4(rHVsn*ArSuGvc!4Ir^$fNujx)Gbyj7enWkIDKf)rk$$!|ljQE^7_jEd>C zwkAm61)BV}1gW)nB^8@U7FF7bgApg=V4>nB4mK*z;@~M2+pCN!?aUyB7ijX^%wVJ9 zw32KUZx!#1`u|vq*{j%QbU~%Pd@$g2KA5Yx%?E20r#QGsM!i)$v5G%GKWTyoP!cqj z?p2x~i5F<{TNBJ#WEKa(uP-V8p$L9Uix~p0j`MX4rOa>lcOq#2RMwMym&))0jediL zFA=GNMP^}(NBtQzMh5|TxrHG=u3_9|An0-H$-XPc3pDx-7P3Zb3qx=a2mJ+J9@ne; zksr6)!~KM-H~9DU`fa~RHV(=X&(P>MSyX6kF$F>9x0t<1*oPVZftvkN$39u$pKfta zSlOriTg6+&Z!djtFCHzT;&&D>xtD;qinog2{U~gawV1t%-}pd#k&Jq)czG4SFQZ1y zWoWJa@#ZpDq9KO-fid<=eX?j_+R2zcx%J;AA@CEhJ1f5Dxqsf>|mQ;gAwZS*1dS z#q-(GV6?zE@b71kR&kiYzRK>es~Gs-D*hW(T>snbV3iZ>s_gco!S@$xFJ+iENvT5GHWs`bZPNZ1&JaNyq-X;jl5l{Y7~ zXkmd`s}K(S3s;7#8_Z&Rmy`sWSxDHNhH&8D+~TS^&cgN!@G!TSKr;)8+xq|x{7*oT z{VirM682$cjzGA zW4#u$SMd}Nv{`4=TgA((n0rRezPXHld)_&Mxkd3>Ya$1D*JpR&TMSz`hdrPI^pIkL zNF=ZccHBiPKo{;=g8eW9+la?~$O7~+pC;H1)UXkI+{rCKC-_N%gQX6(vX6V=1?Z(e zK@cSiXfwcgH-P}V5zY`Cr(1A4#dtr40Q)&k&fq9vMH^DaJ6Z(T5p#mzR`K#G2LHIj zjd|mpeFE$ZJWa4$`hZ&-$9p3M*za+IAbS;q@*CV|b1kFhGI)60`1CmePOrlRQF|Uf zes6ewpDVxh&;MJ1Ap2X)UL@?p%p8H5{Zhv|Szw=TO_8v&PWiWrmsjx|r4P=RY`s|H= Y0XvFr-Nz`a@Bjb+07*qoM6N<$f-ulMivR!s literal 0 HcmV?d00001 diff --git a/yogstation/icons/obj/doors/1x3blast_hor.dmi b/yogstation/icons/obj/doors/1x3blast_hor.dmi new file mode 100644 index 0000000000000000000000000000000000000000..72d8898ae6c97602874abfc3e54623d5244ae03c GIT binary patch literal 1466 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 literal 0 HcmV?d00001 diff --git a/yogstation/icons/obj/doors/1x4blast_hor.dmi b/yogstation/icons/obj/doors/1x4blast_hor.dmi new file mode 100644 index 0000000000000000000000000000000000000000..41165639a9059cc0061e0903ddfa362e15dcfa50 GIT binary patch literal 1624 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 LsO4jGx7dFJkxfO0 literal 0 HcmV?d00001 diff --git a/yogstation/icons/obj/spacepods/2x2.dmi b/yogstation/icons/obj/spacepods/2x2.dmi new file mode 100644 index 0000000000000000000000000000000000000000..ecdb69d66c379973535ccd296a41f797ae6039d4 GIT binary patch literal 1085 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 G5&s9g{0XA~ literal 0 HcmV?d00001