From 3256202407143d8e62bf2e13c339a7c044d42580 Mon Sep 17 00:00:00 2001 From: Neerti Date: Sat, 26 Jan 2019 04:25:17 -0500 Subject: [PATCH] [READY]Ports /tg/station pixel projectiles, processing subsystems, timer subsystems, and some misc stuff to make it all work --- code/__datastructures/globals.dm | 1 + code/__defines/_lists.dm | 2 + code/__defines/flags.dm | 38 +- code/__defines/items_clothing.dm | 3 + code/__defines/math_physics.dm | 2 +- code/__defines/misc.dm | 6 +- code/__defines/subsystems.dm | 1 + code/__defines/tick.dm | 0 code/_helpers/_lists.dm | 1516 +++++++++++++++++ code/_helpers/lists.dm | 758 --------- code/_helpers/mobs.dm | 13 - code/_helpers/sorts/comparators.dm | 3 + code/_helpers/unsorted.dm | 14 + code/_helpers/view.dm | 12 + code/_onclick/click.dm | 13 +- code/_onclick/hud/action.dm | 2 +- .../subsystems/processing/projectiles.dm | 16 + code/datums/datum.dm | 1 + code/datums/position_point_vector.dm | 227 +++ code/game/atoms.dm | 7 +- code/game/atoms_movable.dm | 281 ++- code/game/gamemodes/cult/construct_spells.dm | 9 +- .../technomancer/devices/tesla_armor.dm | 7 +- .../technomancer/spells/energy_siphon.dm | 5 +- .../technomancer/spells/projectile/beam.dm | 6 +- .../spells/projectile/chain_lightning.dm | 6 +- .../spells/projectile/lightning.dm | 6 +- .../spells/projectile/projectile.dm | 3 +- code/game/machinery/doors/door.dm | 4 +- code/game/machinery/doors/windowdoor.dm | 33 +- .../embedded_program_base.dm | 5 +- code/game/machinery/portable_turret.dm | 5 +- code/game/mecha/equipment/weapons/weapons.dm | 9 +- code/game/mecha/mecha.dm | 4 +- code/game/objects/buckling.dm | 3 + .../temporary_visuals/projectiles/impact.dm | 81 + .../temporary_visuals/projectiles/muzzle.dm | 93 + .../projectiles/projectile_effects.dm | 60 + .../temporary_visuals/projectiles/tracer.dm | 109 ++ ...emproary_visual.dm => temporary_visual.dm} | 19 +- .../items/weapons/grenades/explosive.dm | 5 +- .../items/weapons/grenades/projectile.dm | 3 +- .../structures/crates_lockers/closets.dm | 7 +- code/game/objects/structures/window.dm | 11 +- code/game/turfs/turf.dm | 110 +- code/game/world.dm | 639 +++++++ code/global_init.dm | 27 + code/modules/clothing/chameleon.dm | 2 +- code/modules/mob/living/bot/ed209bot.dm | 5 +- code/modules/mob/living/carbon/carbon.dm | 8 +- .../human/species/xenomorphs/alien_powers.dm | 3 +- code/modules/mob/living/living.dm | 154 +- .../mob/living/simple_animal/simple_animal.dm | 8 +- code/modules/mob/living/simple_mob/combat.dm | 3 +- .../animal/giant_spider/webslinger.dm | 3 +- .../mechanical/mecha/adv_dark_gygax.dm | 524 +++--- .../simple_mob/subtypes/slime/feral/feral.dm | 188 +- code/modules/mob/mob.dm | 17 +- code/modules/mob/mob_movement.dm | 20 +- code/modules/mob/new_player/new_player.dm | 2 +- code/modules/power/singularity/act.dm | 2 - code/modules/power/singularity/emitter.dm | 3 +- code/modules/projectiles/gun.dm | 20 +- .../projectiles/guns/projectile/dartgun.dm | 2 +- code/modules/projectiles/guns/vox.dm | 12 +- code/modules/projectiles/projectile.dm | 758 ++++++--- code/modules/projectiles/projectile/arc.dm | 31 +- code/modules/projectiles/projectile/beams.dm | 66 +- .../modules/projectiles/projectile/bullets.dm | 6 +- code/modules/projectiles/projectile/energy.dm | 14 +- code/modules/projectiles/projectile/hook.dm | 6 +- .../projectiles/projectile/magnetic.dm | 12 +- .../modules/projectiles/projectile/pellets.dm | 4 - .../modules/projectiles/projectile/special.dm | 2 +- code/modules/projectiles/projectile/trace.dm | 38 + code/modules/recycling/disposal.dm | 2 +- code/modules/spells/spell_projectile.dm | 2 +- .../spells/targeted/projectile/projectile.dm | 7 +- code/modules/tables/interactions.dm | 2 +- code/modules/turbolift/turbolift_map.dm | 2 +- code/modules/xenobio/items/weapons.dm | 6 +- code/world.dm | 7 + icons/obj/projectiles_impact.dmi | Bin 0 -> 20181 bytes icons/obj/projectiles_muzzle.dmi | Bin 0 -> 20583 bytes icons/obj/projectiles_tracer.dmi | Bin 0 -> 2994 bytes vorestation.dme | 23 +- 86 files changed, 4372 insertions(+), 1777 deletions(-) delete mode 100644 code/__defines/tick.dm create mode 100644 code/_helpers/_lists.dm delete mode 100644 code/_helpers/lists.dm create mode 100644 code/_helpers/view.dm create mode 100644 code/controllers/subsystems/processing/projectiles.dm create mode 100644 code/datums/position_point_vector.dm create mode 100644 code/game/objects/effects/temporary_visuals/projectiles/impact.dm create mode 100644 code/game/objects/effects/temporary_visuals/projectiles/muzzle.dm create mode 100644 code/game/objects/effects/temporary_visuals/projectiles/projectile_effects.dm create mode 100644 code/game/objects/effects/temporary_visuals/projectiles/tracer.dm rename code/game/objects/effects/temporary_visuals/{temproary_visual.dm => temporary_visual.dm} (64%) create mode 100644 code/game/world.dm create mode 100644 code/global_init.dm create mode 100644 code/modules/projectiles/projectile/trace.dm create mode 100644 icons/obj/projectiles_impact.dmi create mode 100644 icons/obj/projectiles_muzzle.dmi create mode 100644 icons/obj/projectiles_tracer.dmi diff --git a/code/__datastructures/globals.dm b/code/__datastructures/globals.dm index 637af7f0fc..05d5a2d29b 100644 --- a/code/__datastructures/globals.dm +++ b/code/__datastructures/globals.dm @@ -36,3 +36,4 @@ #define GLOBAL_LIST(X) GLOBAL_RAW(/list/##X); GLOBAL_MANAGED(X, null) #define GLOBAL_DATUM(X, Typepath) GLOBAL_RAW(Typepath/##X); GLOBAL_MANAGED(X, null) + diff --git a/code/__defines/_lists.dm b/code/__defines/_lists.dm index f011662cec..348aafccef 100644 --- a/code/__defines/_lists.dm +++ b/code/__defines/_lists.dm @@ -52,3 +52,5 @@ __BIN_MID = __BIN_ITEM.##COMPARE > IN.##COMPARE ? __BIN_MID : __BIN_MID + 1;\ LIST.Insert(__BIN_MID, IN);\ } + +#define islist(L) istype(L, /list) diff --git a/code/__defines/flags.dm b/code/__defines/flags.dm index b9417d2a86..5b6152e6d2 100644 --- a/code/__defines/flags.dm +++ b/code/__defines/flags.dm @@ -4,16 +4,42 @@ #define NONE 0 //for convenience -#define ENABLE_BITFIELD(variable, flag) (variable |= (flag)) -#define DISABLE_BITFIELD(variable, flag) (variable &= ~(flag)) -#define CHECK_BITFIELD(variable, flag) (variable & flag) +#define ENABLE_BITFIELD(variable, flag) (variable |= (flag)) +#define DISABLE_BITFIELD(variable, flag) (variable &= ~(flag)) +#define CHECK_BITFIELD(variable, flag) (variable & (flag)) //check if all bitflags specified are present -#define CHECK_MULTIPLE_BITFIELDS(flagvar, flags) ((flagvar & (flags)) == flags) +#define CHECK_MULTIPLE_BITFIELDS(flagvar, flags) ((flagvar & (flags)) == flags) GLOBAL_LIST_INIT(bitflags, list(1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384, 32768)) // datum_flags -#define DF_VAR_EDITED (1<<0) -#define DF_ISPROCESSING (1<<1) +#define DF_VAR_EDITED (1<<0) +#define DF_ISPROCESSING (1<<1) +// /atom/movable movement_type +#define UNSTOPPABLE (1<<0) //Can not be stopped from moving from Cross(), CanPass(), or Uncross() failing. Still bumps everything it passes through, though. + +// Flags bitmasks. - Used in /atom/var/flags +#define NOBLUDGEON (1<<0) // When an item has this it produces no "X has been hit by Y with Z" message with the default handler. +#define CONDUCT (1<<1) // Conducts electricity. (metal etc.) +#define ON_BORDER (1<<2) // Item has priority to check when entering or leaving. +#define NOBLOODY (1<<3) // Used for items if they don't want to get a blood overlay. +#define OPENCONTAINER (1<<4) // Is an open container for chemistry purposes. +#define PHORONGUARD (1<<5) // Does not get contaminated by phoron. +#define NOREACT (1<<6) // Reagents don't react inside this container. +#define PROXMOVE (1<<7)// Does this object require proximity checking in Enter()? +#define OVERLAY_QUEUED (1<<8)// Atom queued to SSoverlay for COMPILE_OVERLAYS + +//Flags for items (equipment) - Used in /obj/item/var/item_flags +#define THICKMATERIAL (1<<0) // Prevents syringes, parapens and hyposprays if equipped to slot_suit or slot_head. +#define AIRTIGHT (1<<1) // Functions with internals. +#define NOSLIP (1<<2) // Prevents from slipping on wet floors, in space, etc. +#define BLOCK_GAS_SMOKE_EFFECT (1<<3) // Blocks the effect that chemical clouds would have on a mob -- glasses, mask and helmets ONLY! (NOTE: flag shared with ONESIZEFITSALL) +#define FLEXIBLEMATERIAL (1<<4) // At the moment, masks with this flag will not prevent eating even if they are covering your face. + +// Flags for pass_flags. - Used in /atom/var/pass_flags +#define PASSTABLE (1<<0) +#define PASSGLASS (1<<1) +#define PASSGRILLE (1<<2) +#define PASSBLOB (1<<3) diff --git a/code/__defines/items_clothing.dm b/code/__defines/items_clothing.dm index 23762695d5..f947eab89a 100644 --- a/code/__defines/items_clothing.dm +++ b/code/__defines/items_clothing.dm @@ -40,6 +40,7 @@ #define ACCESSORY_SLOT_TORSO (ACCESSORY_SLOT_UTILITY|ACCESSORY_SLOT_WEAPON) +<<<<<<< HEAD // Flags bitmasks. - Used in /atom/var/flags #define NOBLUDGEON 0x1 // When an item has this it produces no "X has been hit by Y with Z" message with the default handler. #define CONDUCT 0x2 // Conducts electricity. (metal etc.) @@ -64,6 +65,8 @@ #define PASSGRILLE 0x4 #define PASSBLOB 0x8 +======= +>>>>>>> 9ff8103... Merge pull request #5636 from kevinz000/pixel_projectiles // Bitmasks for the /obj/item/var/flags_inv variable. These determine when a piece of clothing hides another, i.e. a helmet hiding glasses. // WARNING: The following flags apply only to the external suit! #define HIDEGLOVES 0x1 diff --git a/code/__defines/math_physics.dm b/code/__defines/math_physics.dm index b4952f4e11..90950d0ca0 100644 --- a/code/__defines/math_physics.dm +++ b/code/__defines/math_physics.dm @@ -21,4 +21,4 @@ #define QUANTIZE(variable) (round(variable,0.0001)) #define TICKS_IN_DAY (TICKS_IN_SECOND * 60 * 60 * 24) -#define TICKS_IN_SECOND (world.fps) \ No newline at end of file +#define TICKS_IN_SECOND (world.fps) diff --git a/code/__defines/misc.dm b/code/__defines/misc.dm index 91baf41433..0e3234ed83 100644 --- a/code/__defines/misc.dm +++ b/code/__defines/misc.dm @@ -290,6 +290,11 @@ var/global/list/##LIST_NAME = list();\ #define IS_WIRECUTTER "wirecutter" #define IS_WRENCH "wrench" + +// Diagonal movement +#define FIRST_DIAG_STEP 1 +#define SECOND_DIAG_STEP 2 + // RCD modes. Used on the RCD, and gets passed to an object's rcd_act() when an RCD is used on it, to determine what happens. #define RCD_FLOORWALL "Floor / Wall" // Builds plating on space/ground/open tiles. Builds a wall when on floors. Finishes walls when used on girders. #define RCD_AIRLOCK "Airlock" // Builds an airlock on the tile if one isn't already there. @@ -300,7 +305,6 @@ var/global/list/##LIST_NAME = list();\ #define RCD_VALUE_DELAY "delay" #define RCD_VALUE_COST "cost" - #define RCD_SHEETS_PER_MATTER_UNIT 4 // Each physical material sheet is worth four matter units. #define RCD_MAX_CAPACITY 30 * RCD_SHEETS_PER_MATTER_UNIT diff --git a/code/__defines/subsystems.dm b/code/__defines/subsystems.dm index 4f48416da8..ac480237b5 100644 --- a/code/__defines/subsystems.dm +++ b/code/__defines/subsystems.dm @@ -84,6 +84,7 @@ var/global/list/runlevel_flags = list(RUNLEVEL_LOBBY, RUNLEVEL_SETUP, RUNLEVEL_G #define FIRE_PRIORITY_DEFAULT 50 #define FIRE_PRIORITY_PLANETS 75 #define FIRE_PRIORITY_MACHINES 100 +#define FIRE_PRIORITY_PROJECTILES 150 #define FIRE_PRIORITY_OVERLAYS 500 // Macro defining the actual code applying our overlays lists to the BYOND overlays list. (I guess a macro for speed) diff --git a/code/__defines/tick.dm b/code/__defines/tick.dm deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/code/_helpers/_lists.dm b/code/_helpers/_lists.dm new file mode 100644 index 0000000000..9b3c182155 --- /dev/null +++ b/code/_helpers/_lists.dm @@ -0,0 +1,1516 @@ +<<<<<<< HEAD:code/_helpers/lists.dm +/* + * Holds procs to help with list operations + * Contains groups: + * Misc + * Sorting + */ + +/* + * Misc + */ + +//Returns a list in plain english as a string +/proc/english_list(var/list/input, nothing_text = "nothing", and_text = " and ", comma_text = ", ", final_comma_text = "" ) + switch(input.len) + if(0) return nothing_text + if(1) return "[input[1]]" + if(2) return "[input[1]][and_text][input[2]]" + else return "[jointext(input, comma_text, 1, -1)][final_comma_text][and_text][input[input.len]]" + +//Returns list element or null. Should prevent "index out of bounds" error. +proc/listgetindex(var/list/list,index) + if(istype(list) && list.len) + if(isnum(index)) + if(InRange(index,1,list.len)) + return list[index] + else if(index in list) + return list[index] + return + +proc/islist(list/list) + return(istype(list)) + +//Return either pick(list) or null if list is not of type /list or is empty +proc/safepick(list/list) + if(!islist(list) || !list.len) + return + return pick(list) + +//Checks if the list is empty +proc/isemptylist(list/list) + if(!list.len) + return 1 + return 0 + +//Checks for specific types in a list +/proc/is_type_in_list(var/atom/A, var/list/L) + for(var/type in L) + if(istype(A, type)) + return 1 + return 0 + +//Checks for specific paths in a list +/proc/is_path_in_list(var/atom/A, var/list/L) + for(var/path in L) + if(ispath(A, path)) + return 1 + return 0 + +////////////////////////////////////////////////////// +// "typecache" utilities - Making and searching them +////////////////////////////////////////////////////// + +//Checks for specific types in specifically structured (Assoc "type" = TRUE) lists ('typecaches') +/proc/is_type_in_typecache(atom/A, list/L) + if(!LAZYLEN(L) || !A) + return FALSE + return L[A.type] + +//returns a new list with only atoms that are in typecache L +/proc/typecache_filter_list(list/atoms, list/typecache) + . = list() + for(var/thing in atoms) + var/atom/A = thing + if(typecache[A.type]) + . += A + +/proc/typecache_filter_list_reverse(list/atoms, list/typecache) + . = list() + for(var/thing in atoms) + var/atom/A = thing + if(!typecache[A.type]) + . += A + +/proc/typecache_filter_multi_list_exclusion(list/atoms, list/typecache_include, list/typecache_exclude) + . = list() + for(var/thing in atoms) + var/atom/A = thing + if(typecache_include[A.type] && !typecache_exclude[A.type]) + . += A + +//Like typesof() or subtypesof(), but returns a typecache instead of a list +/proc/typecacheof(path, ignore_root_path, only_root_path = FALSE) + if(ispath(path)) + var/list/types = list() + if(only_root_path) + types = list(path) + else + types = ignore_root_path ? subtypesof(path) : typesof(path) + var/list/L = list() + for(var/T in types) + L[T] = TRUE + return L + else if(islist(path)) + var/list/pathlist = path + var/list/L = list() + if(ignore_root_path) + for(var/P in pathlist) + for(var/T in subtypesof(P)) + L[T] = TRUE + else + for(var/P in pathlist) + if(only_root_path) + L[P] = TRUE + else + for(var/T in typesof(P)) + L[T] = TRUE + return L + +////////////////////////////////////////////////////// + +//Empties the list by setting the length to 0. Hopefully the elements get garbage collected +proc/clearlist(list/list) + if(istype(list)) + list.len = 0 + return + +//Removes any null entries from the list +proc/listclearnulls(list/list) + if(istype(list)) + while(null in list) + list -= null + return + +/* + * Returns list containing all the entries from first list that are not present in second. + * If skiprep = 1, repeated elements are treated as one. + * If either of arguments is not a list, returns null + */ +/proc/difflist(var/list/first, var/list/second, var/skiprep=0) + if(!islist(first) || !islist(second)) + return + var/list/result = new + if(skiprep) + for(var/e in first) + if(!(e in result) && !(e in second)) + result += e + else + result = first - second + return result + +/* + * Returns list containing entries that are in either list but not both. + * If skipref = 1, repeated elements are treated as one. + * If either of arguments is not a list, returns null + */ +/proc/uniquemergelist(var/list/first, var/list/second, var/skiprep=0) + if(!islist(first) || !islist(second)) + return + var/list/result = new + if(skiprep) + result = difflist(first, second, skiprep)+difflist(second, first, skiprep) + else + result = first ^ second + return result + +//Pretends to pick an element based on its weight but really just seems to pick a random element. +/proc/pickweight(list/L) + var/total = 0 + var/item + for (item in L) + if (!L[item]) + L[item] = 1 + total += L[item] + + total = rand(1, total) + for (item in L) + total -=L [item] + if (total <= 0) + return item + + return null + +//Pick a random element from the list and remove it from the list. +/proc/pick_n_take(list/listfrom) + if (listfrom.len > 0) + var/picked = pick(listfrom) + listfrom -= picked + return picked + return null + +//Returns the top(last) element from the list and removes it from the list (typical stack function) +/proc/pop(list/listfrom) + if (listfrom.len > 0) + var/picked = listfrom[listfrom.len] + listfrom.len-- + return picked + return null + +//Returns the next element in parameter list after first appearance of parameter element. If it is the last element of the list or not present in list, returns first element. +/proc/next_in_list(element, list/L) + for(var/i=1, i= 1; i--) + output += L[i] + return output + +//Randomize: Return the list in a random order +/proc/shuffle(var/list/L) + if(!L) + return + + L = L.Copy() + + for(var/i=1; i current_index) + current_index++ + current_item = sorted_list[current_index] + + current_item_value = current_item:dd_SortValue() + current_sort_object_value = current_sort_object:dd_SortValue() + if (current_sort_object_value < current_item_value) + high_index = current_index - 1 + else if (current_sort_object_value > current_item_value) + low_index = current_index + 1 + else + // current_sort_object == current_item + low_index = current_index + break + + // Insert before low_index. + insert_index = low_index + + // Special case adding to end of list. + if (insert_index > sorted_list.len) + sorted_list += current_sort_object + continue + + // Because BYOND lists don't support insert, have to do it by: + // 1) taking out bottom of list, 2) adding item, 3) putting back bottom of list. + list_bottom = sorted_list.Copy(insert_index) + sorted_list.Cut(insert_index) + sorted_list += current_sort_object + sorted_list += list_bottom + return sorted_list +*/ + +proc/dd_sortedtextlist(list/incoming, case_sensitive = 0) + // Returns a new list with the text values sorted. + // Use binary search to order by sortValue. + // This works by going to the half-point of the list, seeing if the node in question is higher or lower cost, + // then going halfway up or down the list and checking again. + // This is a very fast way to sort an item into a list. + var/list/sorted_text = new() + var/low_index + var/high_index + var/insert_index + var/midway_calc + var/current_index + var/current_item + var/list/list_bottom + var/sort_result + + var/current_sort_text + for (current_sort_text in incoming) + low_index = 1 + high_index = sorted_text.len + while (low_index <= high_index) + // Figure out the midpoint, rounding up for fractions. (BYOND rounds down, so add 1 if necessary.) + midway_calc = (low_index + high_index) / 2 + current_index = round(midway_calc) + if (midway_calc > current_index) + current_index++ + current_item = sorted_text[current_index] + + if (case_sensitive) + sort_result = sorttextEx(current_sort_text, current_item) + else + sort_result = sorttext(current_sort_text, current_item) + + switch(sort_result) + if (1) + high_index = current_index - 1 // current_sort_text < current_item + if (-1) + low_index = current_index + 1 // current_sort_text > current_item + if (0) + low_index = current_index // current_sort_text == current_item + break + + // Insert before low_index. + insert_index = low_index + + // Special case adding to end of list. + if (insert_index > sorted_text.len) + sorted_text += current_sort_text + continue + + // Because BYOND lists don't support insert, have to do it by: + // 1) taking out bottom of list, 2) adding item, 3) putting back bottom of list. + list_bottom = sorted_text.Copy(insert_index) + sorted_text.Cut(insert_index) + sorted_text += current_sort_text + sorted_text += list_bottom + return sorted_text + + +proc/dd_sortedTextList(list/incoming) + var/case_sensitive = 1 + return dd_sortedtextlist(incoming, case_sensitive) + + +/datum/proc/dd_SortValue() + return "[src]" + +/obj/machinery/dd_SortValue() + return "[sanitize_old(name)]" + +/obj/machinery/camera/dd_SortValue() + return "[c_tag]" + +/datum/alarm/dd_SortValue() + return "[sanitize_old(last_name)]" + +/proc/subtypesof(prototype) + return (typesof(prototype) - prototype) + +//creates every subtype of prototype (excluding prototype) and adds it to list L. +//if no list/L is provided, one is created. +/proc/init_subtypes(prototype, list/L) + if(!istype(L)) L = list() + for(var/path in subtypesof(prototype)) + L += new path() + return L + +//creates every subtype of prototype (excluding prototype) and adds it to list L as a type/instance pair. +//if no list/L is provided, one is created. +/proc/init_subtypes_assoc(prototype, list/L) + if(!istype(L)) L = list() + for(var/path in subtypesof(prototype)) + L[path] = new path() + return L + +//Move a single element from position fromIndex within a list, to position toIndex +//All elements in the range [1,toIndex) before the move will be before the pivot afterwards +//All elements in the range [toIndex, L.len+1) before the move will be after the pivot afterwards +//In other words, it's as if the range [fromIndex,toIndex) have been rotated using a <<< operation common to other languages. +//fromIndex and toIndex must be in the range [1,L.len+1] +//This will preserve associations ~Carnie +/proc/moveElement(list/L, fromIndex, toIndex) + if(fromIndex == toIndex || fromIndex+1 == toIndex) //no need to move + return + if(fromIndex > toIndex) + ++fromIndex //since a null will be inserted before fromIndex, the index needs to be nudged right by one + + L.Insert(toIndex, null) + L.Swap(fromIndex, toIndex) + L.Cut(fromIndex, fromIndex+1) + +//Move elements [fromIndex,fromIndex+len) to [toIndex-len, toIndex) +//Same as moveElement but for ranges of elements +//This will preserve associations ~Carnie +/proc/moveRange(list/L, fromIndex, toIndex, len=1) + var/distance = abs(toIndex - fromIndex) + if(len >= distance) //there are more elements to be moved than the distance to be moved. Therefore the same result can be achieved (with fewer operations) by moving elements between where we are and where we are going. The result being, our range we are moving is shifted left or right by dist elements + if(fromIndex <= toIndex) + return //no need to move + fromIndex += len //we want to shift left instead of right + + for(var/i=0, i toIndex) + fromIndex += len + + for(var/i=0, i 0) + var/picked = pick(listfrom) + listfrom -= picked + return picked + return null + +//Returns the top(last) element from the list and removes it from the list (typical stack function) +/proc/pop(list/listfrom) + if (listfrom.len > 0) + var/picked = listfrom[listfrom.len] + listfrom.len-- + return picked + return null + +//Returns the next element in parameter list after first appearance of parameter element. If it is the last element of the list or not present in list, returns first element. +/proc/next_in_list(element, list/L) + for(var/i=1, i= 1; i--) + output += L[i] + return output + +//Randomize: Return the list in a random order +/proc/shuffle(var/list/L) + if(!L) + return + + L = L.Copy() + + for(var/i=1; i current_index) + current_index++ + current_item = sorted_list[current_index] + + current_item_value = current_item:dd_SortValue() + current_sort_object_value = current_sort_object:dd_SortValue() + if (current_sort_object_value < current_item_value) + high_index = current_index - 1 + else if (current_sort_object_value > current_item_value) + low_index = current_index + 1 + else + // current_sort_object == current_item + low_index = current_index + break + + // Insert before low_index. + insert_index = low_index + + // Special case adding to end of list. + if (insert_index > sorted_list.len) + sorted_list += current_sort_object + continue + + // Because BYOND lists don't support insert, have to do it by: + // 1) taking out bottom of list, 2) adding item, 3) putting back bottom of list. + list_bottom = sorted_list.Copy(insert_index) + sorted_list.Cut(insert_index) + sorted_list += current_sort_object + sorted_list += list_bottom + return sorted_list +*/ + +proc/dd_sortedtextlist(list/incoming, case_sensitive = 0) + // Returns a new list with the text values sorted. + // Use binary search to order by sortValue. + // This works by going to the half-point of the list, seeing if the node in question is higher or lower cost, + // then going halfway up or down the list and checking again. + // This is a very fast way to sort an item into a list. + var/list/sorted_text = new() + var/low_index + var/high_index + var/insert_index + var/midway_calc + var/current_index + var/current_item + var/list/list_bottom + var/sort_result + + var/current_sort_text + for (current_sort_text in incoming) + low_index = 1 + high_index = sorted_text.len + while (low_index <= high_index) + // Figure out the midpoint, rounding up for fractions. (BYOND rounds down, so add 1 if necessary.) + midway_calc = (low_index + high_index) / 2 + current_index = round(midway_calc) + if (midway_calc > current_index) + current_index++ + current_item = sorted_text[current_index] + + if (case_sensitive) + sort_result = sorttextEx(current_sort_text, current_item) + else + sort_result = sorttext(current_sort_text, current_item) + + switch(sort_result) + if (1) + high_index = current_index - 1 // current_sort_text < current_item + if (-1) + low_index = current_index + 1 // current_sort_text > current_item + if (0) + low_index = current_index // current_sort_text == current_item + break + + // Insert before low_index. + insert_index = low_index + + // Special case adding to end of list. + if (insert_index > sorted_text.len) + sorted_text += current_sort_text + continue + + // Because BYOND lists don't support insert, have to do it by: + // 1) taking out bottom of list, 2) adding item, 3) putting back bottom of list. + list_bottom = sorted_text.Copy(insert_index) + sorted_text.Cut(insert_index) + sorted_text += current_sort_text + sorted_text += list_bottom + return sorted_text + + +proc/dd_sortedTextList(list/incoming) + var/case_sensitive = 1 + return dd_sortedtextlist(incoming, case_sensitive) + + +/datum/proc/dd_SortValue() + return "[src]" + +/obj/machinery/dd_SortValue() + return "[sanitize_old(name)]" + +/obj/machinery/camera/dd_SortValue() + return "[c_tag]" + +/datum/alarm/dd_SortValue() + return "[sanitize_old(last_name)]" + +/proc/subtypesof(prototype) + return (typesof(prototype) - prototype) + +//creates every subtype of prototype (excluding prototype) and adds it to list L. +//if no list/L is provided, one is created. +/proc/init_subtypes(prototype, list/L) + if(!istype(L)) L = list() + for(var/path in subtypesof(prototype)) + L += new path() + return L + +//creates every subtype of prototype (excluding prototype) and adds it to list L as a type/instance pair. +//if no list/L is provided, one is created. +/proc/init_subtypes_assoc(prototype, list/L) + if(!istype(L)) L = list() + for(var/path in subtypesof(prototype)) + L[path] = new path() + return L + +//Move a single element from position fromIndex within a list, to position toIndex +//All elements in the range [1,toIndex) before the move will be before the pivot afterwards +//All elements in the range [toIndex, L.len+1) before the move will be after the pivot afterwards +//In other words, it's as if the range [fromIndex,toIndex) have been rotated using a <<< operation common to other languages. +//fromIndex and toIndex must be in the range [1,L.len+1] +//This will preserve associations ~Carnie +/proc/moveElement(list/L, fromIndex, toIndex) + if(fromIndex == toIndex || fromIndex+1 == toIndex) //no need to move + return + if(fromIndex > toIndex) + ++fromIndex //since a null will be inserted before fromIndex, the index needs to be nudged right by one + + L.Insert(toIndex, null) + L.Swap(fromIndex, toIndex) + L.Cut(fromIndex, fromIndex+1) + +//Move elements [fromIndex,fromIndex+len) to [toIndex-len, toIndex) +//Same as moveElement but for ranges of elements +//This will preserve associations ~Carnie +/proc/moveRange(list/L, fromIndex, toIndex, len=1) + var/distance = abs(toIndex - fromIndex) + if(len >= distance) //there are more elements to be moved than the distance to be moved. Therefore the same result can be achieved (with fewer operations) by moving elements between where we are and where we are going. The result being, our range we are moving is shifted left or right by dist elements + if(fromIndex <= toIndex) + return //no need to move + fromIndex += len //we want to shift left instead of right + + for(var/i=0, i toIndex) + fromIndex += len + + for(var/i=0, i>>>>>> 9ff8103... Merge pull request #5636 from kevinz000/pixel_projectiles:code/_helpers/_lists.dm diff --git a/code/_helpers/lists.dm b/code/_helpers/lists.dm deleted file mode 100644 index b50db3457c..0000000000 --- a/code/_helpers/lists.dm +++ /dev/null @@ -1,758 +0,0 @@ -/* - * Holds procs to help with list operations - * Contains groups: - * Misc - * Sorting - */ - -/* - * Misc - */ - -//Returns a list in plain english as a string -/proc/english_list(var/list/input, nothing_text = "nothing", and_text = " and ", comma_text = ", ", final_comma_text = "" ) - switch(input.len) - if(0) return nothing_text - if(1) return "[input[1]]" - if(2) return "[input[1]][and_text][input[2]]" - else return "[jointext(input, comma_text, 1, -1)][final_comma_text][and_text][input[input.len]]" - -//Returns list element or null. Should prevent "index out of bounds" error. -proc/listgetindex(var/list/list,index) - if(istype(list) && list.len) - if(isnum(index)) - if(InRange(index,1,list.len)) - return list[index] - else if(index in list) - return list[index] - return - -//Return either pick(list) or null if list is not of type /list or is empty -proc/safepick(list/list) - if(!islist(list) || !list.len) - return - return pick(list) - -//Checks if the list is empty -proc/isemptylist(list/list) - if(!list.len) - return 1 - return 0 - -//Checks for specific types in a list -/proc/is_type_in_list(var/atom/A, var/list/L) - for(var/type in L) - if(istype(A, type)) - return 1 - return 0 - -//Checks for specific paths in a list -/proc/is_path_in_list(var/atom/A, var/list/L) - for(var/path in L) - if(ispath(A, path)) - return 1 - return 0 - -////////////////////////////////////////////////////// -// "typecache" utilities - Making and searching them -////////////////////////////////////////////////////// - -//Checks for specific types in specifically structured (Assoc "type" = TRUE) lists ('typecaches') -/proc/is_type_in_typecache(atom/A, list/L) - if(!LAZYLEN(L) || !A) - return FALSE - return L[A.type] - -//returns a new list with only atoms that are in typecache L -/proc/typecache_filter_list(list/atoms, list/typecache) - . = list() - for(var/thing in atoms) - var/atom/A = thing - if(typecache[A.type]) - . += A - -/proc/typecache_filter_list_reverse(list/atoms, list/typecache) - . = list() - for(var/thing in atoms) - var/atom/A = thing - if(!typecache[A.type]) - . += A - -/proc/typecache_filter_multi_list_exclusion(list/atoms, list/typecache_include, list/typecache_exclude) - . = list() - for(var/thing in atoms) - var/atom/A = thing - if(typecache_include[A.type] && !typecache_exclude[A.type]) - . += A - -//Like typesof() or subtypesof(), but returns a typecache instead of a list -/proc/typecacheof(path, ignore_root_path, only_root_path = FALSE) - if(ispath(path)) - var/list/types = list() - if(only_root_path) - types = list(path) - else - types = ignore_root_path ? subtypesof(path) : typesof(path) - var/list/L = list() - for(var/T in types) - L[T] = TRUE - return L - else if(islist(path)) - var/list/pathlist = path - var/list/L = list() - if(ignore_root_path) - for(var/P in pathlist) - for(var/T in subtypesof(P)) - L[T] = TRUE - else - for(var/P in pathlist) - if(only_root_path) - L[P] = TRUE - else - for(var/T in typesof(P)) - L[T] = TRUE - return L - -////////////////////////////////////////////////////// - -//Empties the list by setting the length to 0. Hopefully the elements get garbage collected -proc/clearlist(list/list) - if(istype(list)) - list.len = 0 - return - -//Removes any null entries from the list -proc/listclearnulls(list/list) - if(istype(list)) - while(null in list) - list -= null - return - -/* - * Returns list containing all the entries from first list that are not present in second. - * If skiprep = 1, repeated elements are treated as one. - * If either of arguments is not a list, returns null - */ -/proc/difflist(var/list/first, var/list/second, var/skiprep=0) - if(!islist(first) || !islist(second)) - return - var/list/result = new - if(skiprep) - for(var/e in first) - if(!(e in result) && !(e in second)) - result += e - else - result = first - second - return result - -/* - * Returns list containing entries that are in either list but not both. - * If skipref = 1, repeated elements are treated as one. - * If either of arguments is not a list, returns null - */ -/proc/uniquemergelist(var/list/first, var/list/second, var/skiprep=0) - if(!islist(first) || !islist(second)) - return - var/list/result = new - if(skiprep) - result = difflist(first, second, skiprep)+difflist(second, first, skiprep) - else - result = first ^ second - return result - -//Pretends to pick an element based on its weight but really just seems to pick a random element. -/proc/pickweight(list/L) - var/total = 0 - var/item - for (item in L) - if (!L[item]) - L[item] = 1 - total += L[item] - - total = rand(1, total) - for (item in L) - total -=L [item] - if (total <= 0) - return item - - return null - -//Pick a random element from the list and remove it from the list. -/proc/pick_n_take(list/listfrom) - if (listfrom.len > 0) - var/picked = pick(listfrom) - listfrom -= picked - return picked - return null - -//Returns the top(last) element from the list and removes it from the list (typical stack function) -/proc/pop(list/listfrom) - if (listfrom.len > 0) - var/picked = listfrom[listfrom.len] - listfrom.len-- - return picked - return null - -//Returns the next element in parameter list after first appearance of parameter element. If it is the last element of the list or not present in list, returns first element. -/proc/next_in_list(element, list/L) - for(var/i=1, i= 1; i--) - output += L[i] - return output - -//Randomize: Return the list in a random order -/proc/shuffle(var/list/L) - if(!L) - return - - L = L.Copy() - - for(var/i=1; i current_index) - current_index++ - current_item = sorted_list[current_index] - - current_item_value = current_item:dd_SortValue() - current_sort_object_value = current_sort_object:dd_SortValue() - if (current_sort_object_value < current_item_value) - high_index = current_index - 1 - else if (current_sort_object_value > current_item_value) - low_index = current_index + 1 - else - // current_sort_object == current_item - low_index = current_index - break - - // Insert before low_index. - insert_index = low_index - - // Special case adding to end of list. - if (insert_index > sorted_list.len) - sorted_list += current_sort_object - continue - - // Because BYOND lists don't support insert, have to do it by: - // 1) taking out bottom of list, 2) adding item, 3) putting back bottom of list. - list_bottom = sorted_list.Copy(insert_index) - sorted_list.Cut(insert_index) - sorted_list += current_sort_object - sorted_list += list_bottom - return sorted_list -*/ - -proc/dd_sortedtextlist(list/incoming, case_sensitive = 0) - // Returns a new list with the text values sorted. - // Use binary search to order by sortValue. - // This works by going to the half-point of the list, seeing if the node in question is higher or lower cost, - // then going halfway up or down the list and checking again. - // This is a very fast way to sort an item into a list. - var/list/sorted_text = new() - var/low_index - var/high_index - var/insert_index - var/midway_calc - var/current_index - var/current_item - var/list/list_bottom - var/sort_result - - var/current_sort_text - for (current_sort_text in incoming) - low_index = 1 - high_index = sorted_text.len - while (low_index <= high_index) - // Figure out the midpoint, rounding up for fractions. (BYOND rounds down, so add 1 if necessary.) - midway_calc = (low_index + high_index) / 2 - current_index = round(midway_calc) - if (midway_calc > current_index) - current_index++ - current_item = sorted_text[current_index] - - if (case_sensitive) - sort_result = sorttextEx(current_sort_text, current_item) - else - sort_result = sorttext(current_sort_text, current_item) - - switch(sort_result) - if (1) - high_index = current_index - 1 // current_sort_text < current_item - if (-1) - low_index = current_index + 1 // current_sort_text > current_item - if (0) - low_index = current_index // current_sort_text == current_item - break - - // Insert before low_index. - insert_index = low_index - - // Special case adding to end of list. - if (insert_index > sorted_text.len) - sorted_text += current_sort_text - continue - - // Because BYOND lists don't support insert, have to do it by: - // 1) taking out bottom of list, 2) adding item, 3) putting back bottom of list. - list_bottom = sorted_text.Copy(insert_index) - sorted_text.Cut(insert_index) - sorted_text += current_sort_text - sorted_text += list_bottom - return sorted_text - - -proc/dd_sortedTextList(list/incoming) - var/case_sensitive = 1 - return dd_sortedtextlist(incoming, case_sensitive) - - -/datum/proc/dd_SortValue() - return "[src]" - -/obj/machinery/dd_SortValue() - return "[sanitize_old(name)]" - -/obj/machinery/camera/dd_SortValue() - return "[c_tag]" - -/datum/alarm/dd_SortValue() - return "[sanitize_old(last_name)]" - -/proc/subtypesof(prototype) - return (typesof(prototype) - prototype) - -//creates every subtype of prototype (excluding prototype) and adds it to list L. -//if no list/L is provided, one is created. -/proc/init_subtypes(prototype, list/L) - if(!istype(L)) L = list() - for(var/path in subtypesof(prototype)) - L += new path() - return L - -//creates every subtype of prototype (excluding prototype) and adds it to list L as a type/instance pair. -//if no list/L is provided, one is created. -/proc/init_subtypes_assoc(prototype, list/L) - if(!istype(L)) L = list() - for(var/path in subtypesof(prototype)) - L[path] = new path() - return L - -//Move a single element from position fromIndex within a list, to position toIndex -//All elements in the range [1,toIndex) before the move will be before the pivot afterwards -//All elements in the range [toIndex, L.len+1) before the move will be after the pivot afterwards -//In other words, it's as if the range [fromIndex,toIndex) have been rotated using a <<< operation common to other languages. -//fromIndex and toIndex must be in the range [1,L.len+1] -//This will preserve associations ~Carnie -/proc/moveElement(list/L, fromIndex, toIndex) - if(fromIndex == toIndex || fromIndex+1 == toIndex) //no need to move - return - if(fromIndex > toIndex) - ++fromIndex //since a null will be inserted before fromIndex, the index needs to be nudged right by one - - L.Insert(toIndex, null) - L.Swap(fromIndex, toIndex) - L.Cut(fromIndex, fromIndex+1) - -//Move elements [fromIndex,fromIndex+len) to [toIndex-len, toIndex) -//Same as moveElement but for ranges of elements -//This will preserve associations ~Carnie -/proc/moveRange(list/L, fromIndex, toIndex, len=1) - var/distance = abs(toIndex - fromIndex) - if(len >= distance) //there are more elements to be moved than the distance to be moved. Therefore the same result can be achieved (with fewer operations) by moving elements between where we are and where we are going. The result being, our range we are moving is shifted left or right by dist elements - if(fromIndex <= toIndex) - return //no need to move - fromIndex += len //we want to shift left instead of right - - for(var/i=0, i toIndex) - fromIndex += len - - for(var/i=0, i0) ..() nutrition = max(nutrition - rand(1,5),0) diff --git a/code/_onclick/hud/action.dm b/code/_onclick/hud/action.dm index 2b29fa6492..b5299cd77b 100644 --- a/code/_onclick/hud/action.dm +++ b/code/_onclick/hud/action.dm @@ -82,7 +82,7 @@ /datum/action/proc/Deactivate() return -/datum/action/proc/Process() +/datum/action/process() return /datum/action/proc/CheckRemoval(mob/living/user) // 1 if action is no longer valid for this mob and should be removed diff --git a/code/controllers/subsystems/processing/projectiles.dm b/code/controllers/subsystems/processing/projectiles.dm new file mode 100644 index 0000000000..87c9f097de --- /dev/null +++ b/code/controllers/subsystems/processing/projectiles.dm @@ -0,0 +1,16 @@ +PROCESSING_SUBSYSTEM_DEF(projectiles) + name = "Projectiles" + wait = 1 + stat_tag = "PP" + priority = FIRE_PRIORITY_PROJECTILES + flags = SS_NO_INIT|SS_TICKER + var/global_max_tick_moves = 10 + var/global_pixel_speed = 2 + var/global_iterations_per_move = 16 + +/datum/controller/subsystem/processing/projectiles/proc/set_pixel_speed(new_speed) + global_pixel_speed = new_speed + for(var/i in processing) + var/obj/item/projectile/P = i + if(istype(P)) //there's non projectiles on this too. + P.set_pixel_speed(new_speed) diff --git a/code/datums/datum.dm b/code/datums/datum.dm index 0e82d4d8ca..61eb4c6953 100644 --- a/code/datums/datum.dm +++ b/code/datums/datum.dm @@ -29,6 +29,7 @@ qdel(timer) weakref = null // Clear this reference to ensure it's kept for as brief duration as possible. + tag = null SSnanoui.close_uis(src) return QDEL_HINT_QUEUE diff --git a/code/datums/position_point_vector.dm b/code/datums/position_point_vector.dm new file mode 100644 index 0000000000..c5c392e126 --- /dev/null +++ b/code/datums/position_point_vector.dm @@ -0,0 +1,227 @@ +//Designed for things that need precision trajectories like projectiles. +//Don't use this for anything that you don't absolutely have to use this with (like projectiles!) because it isn't worth using a datum unless you need accuracy down to decimal places in pixels. + +//You might see places where it does - 16 - 1. This is intentionally 17 instead of 16, because of how byond's tiles work and how not doing it will result in rounding errors like things getting put on the wrong turf. + +#define RETURN_PRECISE_POSITION(A) new /datum/position(A) +#define RETURN_PRECISE_POINT(A) new /datum/point(A) + +#define RETURN_POINT_VECTOR(ATOM, ANGLE, SPEED) {new /datum/point/vector(ATOM, null, null, null, null, ANGLE, SPEED)} +#define RETURN_POINT_VECTOR_INCREMENT(ATOM, ANGLE, SPEED, AMT) {new /datum/point/vector(ATOM, null, null, null, null, ANGLE, SPEED, AMT)} + +/datum/position //For positions with map x/y/z and pixel x/y so you don't have to return lists. Could use addition/subtraction in the future I guess. + var/x = 0 + var/y = 0 + var/z = 0 + var/pixel_x = 0 + var/pixel_y = 0 + +/datum/position/proc/valid() + return x && y && z && !isnull(pixel_x) && !isnull(pixel_y) + +/datum/position/New(_x = 0, _y = 0, _z = 0, _pixel_x = 0, _pixel_y = 0) //first argument can also be a /datum/point. + if(istype(_x, /datum/point)) + var/datum/point/P = _x + var/turf/T = P.return_turf() + _x = T.x + _y = T.y + _z = T.z + _pixel_x = P.return_px() + _pixel_y = P.return_py() + else if(istype(_x, /atom)) + var/atom/A = _x + _x = A.x + _y = A.y + _z = A.z + _pixel_x = A.pixel_x + _pixel_y = A.pixel_y + x = _x + y = _y + z = _z + pixel_x = _pixel_x + pixel_y = _pixel_y + +/datum/position/proc/return_turf() + return locate(x, y, z) + +/datum/position/proc/return_px() + return pixel_x + +/datum/position/proc/return_py() + return pixel_y + +/datum/position/proc/return_point() + return new /datum/point(src) + +/proc/point_midpoint_points(datum/point/a, datum/point/b) //Obviously will not support multiZ calculations! Same for the two below. + var/datum/point/P = new + P.x = a.x + (b.x - a.x) / 2 + P.y = a.y + (b.y - a.y) / 2 + P.z = a.z + return P + +/proc/pixel_length_between_points(datum/point/a, datum/point/b) + return sqrt(((b.x - a.x) ** 2) + ((b.y - a.y) ** 2)) + +/proc/angle_between_points(datum/point/a, datum/point/b) + return ATAN2((b.y - a.y), (b.x - a.x)) + +/datum/point //A precise point on the map in absolute pixel locations based on world.icon_size. Pixels are FROM THE EDGE OF THE MAP! + var/x = 0 + var/y = 0 + var/z = 0 + +/datum/point/proc/valid() + return x && y && z + +/datum/point/proc/copy_to(datum/point/p = new) + p.x = x + p.y = y + p.z = z + return p + +/datum/point/New(_x, _y, _z, _pixel_x = 0, _pixel_y = 0) //first argument can also be a /datum/position or /atom. + if(istype(_x, /datum/position)) + var/datum/position/P = _x + _x = P.x + _y = P.y + _z = P.z + _pixel_x = P.pixel_x + _pixel_y = P.pixel_y + else if(istype(_x, /atom)) + var/atom/A = _x + _x = A.x + _y = A.y + _z = A.z + _pixel_x = A.pixel_x + _pixel_y = A.pixel_y + initialize_location(_x, _y, _z, _pixel_x, _pixel_y) + +/datum/point/proc/initialize_location(tile_x, tile_y, tile_z, p_x = 0, p_y = 0) + if(!isnull(tile_x)) + x = ((tile_x - 1) * world.icon_size) + world.icon_size / 2 + p_x + 1 + if(!isnull(tile_y)) + y = ((tile_y - 1) * world.icon_size) + world.icon_size / 2 + p_y + 1 + if(!isnull(tile_z)) + z = tile_z + +/datum/point/proc/debug_out() + var/turf/T = return_turf() + return "\ref[src] aX [x] aY [y] aZ [z] pX [return_px()] pY [return_py()] mX [T.x] mY [T.y] mZ [T.z]" + +/datum/point/proc/move_atom_to_src(atom/movable/AM) + AM.forceMove(return_turf()) + AM.pixel_x = return_px() + AM.pixel_y = return_py() + +/datum/point/proc/return_turf() + return locate(CEILING(x / world.icon_size, 1), CEILING(y / world.icon_size, 1), z) + +/datum/point/proc/return_coordinates() //[turf_x, turf_y, z] + return list(CEILING(x / world.icon_size, 1), CEILING(y / world.icon_size, 1), z) + +/datum/point/proc/return_position() + return new /datum/position(src) + +/datum/point/proc/return_px() + return MODULUS(x, world.icon_size) - 16 - 1 + +/datum/point/proc/return_py() + return MODULUS(y, world.icon_size) - 16 - 1 + + +/datum/point/vector + var/speed = 32 //pixels per iteration + var/iteration = 0 + var/angle = 0 + var/mpx = 0 //calculated x/y movement amounts to prevent having to do trig every step. + var/mpy = 0 + var/starting_x = 0 //just like before, pixels from EDGE of map! This is set in initialize_location(). + var/starting_y = 0 + var/starting_z = 0 + +/datum/point/vector/New(_x, _y, _z, _pixel_x = 0, _pixel_y = 0, _angle, _speed, initial_increment = 0) + ..() + initialize_trajectory(_speed, _angle) + if(initial_increment) + increment(initial_increment) + +/datum/point/vector/initialize_location(tile_x, tile_y, tile_z, p_x = 0, p_y = 0) + . = ..() + starting_x = x + starting_y = y + starting_z = z + +/datum/point/vector/copy_to(datum/point/vector/v = new) + ..(v) + v.speed = speed + v.iteration = iteration + v.angle = angle + v.mpx = mpx + v.mpy = mpy + v.starting_x = starting_x + v.starting_y = starting_y + v.starting_z = starting_z + return v + +/datum/point/vector/proc/initialize_trajectory(pixel_speed, new_angle) + if(!isnull(pixel_speed)) + speed = pixel_speed + set_angle(new_angle) + +/datum/point/vector/proc/set_angle(new_angle) //calculations use "byond angle" where north is 0 instead of 90, and south is 180 instead of 270. + if(isnull(angle)) + return + angle = new_angle + update_offsets() + +/datum/point/vector/proc/update_offsets() + mpx = sin(angle) * speed + mpy = cos(angle) * speed + +/datum/point/vector/proc/set_speed(new_speed) + if(isnull(new_speed) || speed == new_speed) + return + speed = new_speed + update_offsets() + +/datum/point/vector/proc/increment(multiplier = 1) + iteration++ + x += mpx * multiplier + y += mpy * multiplier + +/datum/point/vector/proc/return_vector_after_increments(amount = 7, multiplier = 1, force_simulate = FALSE) + var/datum/point/vector/v = copy_to() + if(force_simulate) + for(var/i in 1 to amount) + v.increment(multiplier) + else + v.increment(multiplier * amount) + return v + +/datum/point/vector/proc/on_z_change() + return + +/datum/point/vector/processed //pixel_speed is per decisecond. + var/last_process = 0 + var/last_move = 0 + var/paused = FALSE + +/datum/point/vector/processed/Destroy() + STOP_PROCESSING(SSprojectiles, src) + return ..() + +/datum/point/vector/processed/proc/start() + last_process = world.time + last_move = world.time + START_PROCESSING(SSprojectiles, src) + +/datum/point/vector/processed/process() + if(paused) + last_move += world.time - last_process + last_process = world.time + return + var/needed_time = world.time - last_move + last_process = world.time + last_move = world.time + increment(needed_time / SSprojectiles.wait) \ No newline at end of file diff --git a/code/game/atoms.dm b/code/game/atoms.dm index bc07de8f40..6929943de2 100644 --- a/code/game/atoms.dm +++ b/code/game/atoms.dm @@ -8,13 +8,14 @@ var/list/blood_DNA var/was_bloodied var/blood_color - var/last_bumped = 0 var/pass_flags = 0 var/throwpass = 0 var/germ_level = GERM_LEVEL_AMBIENT // The higher the germ level, the more germ on the atom. var/simulated = 1 //filter for actions - used by lighting overlays var/fluorescent // Shows up under a UV light. + var/last_bumped = 0 + ///Chemistry. var/datum/reagents/reagents = null @@ -98,7 +99,7 @@ return -1 /atom/proc/Bumped(AM as mob|obj) - return + set waitfor = FALSE // Convenience proc to see if a container is open for chemistry handling // returns true if open @@ -496,7 +497,7 @@ if(!istype(drop_destination) || drop_destination == destination) return forceMove(destination) destination = drop_destination - return forceMove(null) + return moveToNullspace() /atom/proc/onDropInto(var/atom/movable/AM) return // If onDropInto returns null, then dropInto will forceMove AM into us. diff --git a/code/game/atoms_movable.dm b/code/game/atoms_movable.dm index b06a1db623..c81820c0f8 100644 --- a/code/game/atoms_movable.dm +++ b/code/game/atoms_movable.dm @@ -4,6 +4,7 @@ var/last_move = null var/anchored = 0 // var/elevation = 2 - not used anywhere + var/moving_diagonally var/move_speed = 10 var/l_move_time = 1 var/m_flag = 1 @@ -21,6 +22,7 @@ var/old_y = 0 var/datum/riding/riding_datum //VOREStation Add - Moved from /obj/vehicle var/does_spin = TRUE // Does the atom spin when thrown (of course it does :P) + var/movement_type = NONE /atom/movable/Destroy() . = ..() @@ -33,7 +35,7 @@ if(opacity && isturf(loc)) un_opaque = loc - loc = null + moveToNullspace() if(un_opaque) un_opaque.recalc_atom_opacity() if (pulledby) @@ -42,56 +44,234 @@ pulledby = null QDEL_NULL(riding_datum) //VOREStation Add -/atom/movable/vv_edit_var(var_name, var_value) - if(GLOB.VVpixelmovement[var_name]) //Pixel movement is not yet implemented, changing this will break everything irreversibly. - return FALSE - return ..() - -/atom/movable/Bump(var/atom/A, yes) - if(src.throwing) - src.throw_impact(A) - src.throwing = 0 - - spawn(0) - if ((A && yes)) - A.last_bumped = world.time - A.Bumped(src) +//////////////////////////////////////// +// Here's where we rewrite how byond handles movement except slightly different +// To be removed on step_ conversion +// All this work to prevent a second bump +/atom/movable/Move(atom/newloc, direct=0) + . = FALSE + if(!newloc || newloc == loc) return - ..() + + if(!direct) + direct = get_dir(src, newloc) + set_dir(direct) + + if(!loc.Exit(src, newloc)) + return + + if(!newloc.Enter(src, src.loc)) + return + // Past this is the point of no return + var/atom/oldloc = loc + var/area/oldarea = get_area(oldloc) + var/area/newarea = get_area(newloc) + loc = newloc + . = TRUE + oldloc.Exited(src, newloc) + if(oldarea != newarea) + oldarea.Exited(src, newloc) + + for(var/i in oldloc) + if(i == src) // Multi tile objects + continue + var/atom/movable/thing = i + thing.Uncrossed(src) + + newloc.Entered(src, oldloc) + if(oldarea != newarea) + newarea.Entered(src, oldloc) + + for(var/i in loc) + if(i == src) // Multi tile objects + continue + var/atom/movable/thing = i + thing.Crossed(src) +// +//////////////////////////////////////// + +/atom/movable/Move(atom/newloc, direct) + if(!loc || !newloc) + return FALSE + var/atom/oldloc = loc + + if(loc != newloc) + if (!(direct & (direct - 1))) //Cardinal move + . = ..() + else //Diagonal move, split it into cardinal moves + moving_diagonally = FIRST_DIAG_STEP + var/first_step_dir + // The `&& moving_diagonally` checks are so that a forceMove taking + // place due to a Crossed, Bumped, etc. call will interrupt + // the second half of the diagonal movement, or the second attempt + // at a first half if step() fails because we hit something. + if (direct & NORTH) + if (direct & EAST) + if (step(src, NORTH) && moving_diagonally) + first_step_dir = NORTH + moving_diagonally = SECOND_DIAG_STEP + . = step(src, EAST) + else if (moving_diagonally && step(src, EAST)) + first_step_dir = EAST + moving_diagonally = SECOND_DIAG_STEP + . = step(src, NORTH) + else if (direct & WEST) + if (step(src, NORTH) && moving_diagonally) + first_step_dir = NORTH + moving_diagonally = SECOND_DIAG_STEP + . = step(src, WEST) + else if (moving_diagonally && step(src, WEST)) + first_step_dir = WEST + moving_diagonally = SECOND_DIAG_STEP + . = step(src, NORTH) + else if (direct & SOUTH) + if (direct & EAST) + if (step(src, SOUTH) && moving_diagonally) + first_step_dir = SOUTH + moving_diagonally = SECOND_DIAG_STEP + . = step(src, EAST) + else if (moving_diagonally && step(src, EAST)) + first_step_dir = EAST + moving_diagonally = SECOND_DIAG_STEP + . = step(src, SOUTH) + else if (direct & WEST) + if (step(src, SOUTH) && moving_diagonally) + first_step_dir = SOUTH + moving_diagonally = SECOND_DIAG_STEP + . = step(src, WEST) + else if (moving_diagonally && step(src, WEST)) + first_step_dir = WEST + moving_diagonally = SECOND_DIAG_STEP + . = step(src, SOUTH) + if(moving_diagonally == SECOND_DIAG_STEP) + if(!.) + set_dir(first_step_dir) + //else if (!inertia_moving) + // inertia_next_move = world.time + inertia_move_delay + // newtonian_move(direct) + moving_diagonally = 0 + return + + if(!loc || (loc == oldloc && oldloc != newloc)) + last_move = 0 + return + + if(.) + Moved(oldloc, direct) + + //Polaris stuff + move_speed = world.time - l_move_time + l_move_time = world.time + m_flag = 1 + //End + + last_move = direct + set_dir(direct) + if(. && has_buckled_mobs() && !handle_buckled_mob_movement(loc,direct)) //movement failed due to buckled mob(s) + return FALSE + +//Called after a successful Move(). By this point, we've already moved +/atom/movable/proc/Moved(atom/OldLoc, Dir, Forced = FALSE) + //if (!inertia_moving) + // inertia_next_move = world.time + inertia_move_delay + // newtonian_move(Dir) + //if (length(client_mobs_in_contents)) + // update_parallax_contents() + + return TRUE + +// Make sure you know what you're doing if you call this, this is intended to only be called by byond directly. +// You probably want CanPass() +/atom/movable/Cross(atom/movable/AM) + . = TRUE + return CanPass(AM, loc) + +//oldloc = old location on atom, inserted when forceMove is called and ONLY when forceMove is called! +/atom/movable/Crossed(atom/movable/AM, oldloc) return +/atom/movable/Uncross(atom/movable/AM, atom/newloc) + . = ..() + if(isturf(newloc) && !CheckExit(AM, newloc)) + return FALSE + +/atom/movable/Bump(atom/A) + if(!A) + CRASH("Bump was called with no argument.") + . = ..() + if(throwing) + throw_impact(A) + throwing = 0 + if(QDELETED(A)) + return + A.Bumped(src) + A.last_bumped = world.time + /atom/movable/proc/forceMove(atom/destination) - if(loc == destination) - return 0 - var/is_origin_turf = isturf(loc) - var/is_destination_turf = isturf(destination) - // It is a new area if: - // Both the origin and destination are turfs with different areas. - // When either origin or destination is a turf and the other is not. - var/is_new_area = (is_origin_turf ^ is_destination_turf) || (is_origin_turf && is_destination_turf && loc.loc != destination.loc) - - var/atom/origin = loc - loc = destination - - if(origin) - origin.Exited(src, destination) - if(is_origin_turf) - for(var/atom/movable/AM in origin) - AM.Uncrossed(src) - if(is_new_area && is_origin_turf) - origin.loc.Exited(src, destination) - + . = FALSE if(destination) - destination.Entered(src, origin) - if(is_destination_turf) // If we're entering a turf, cross all movable atoms - for(var/atom/movable/AM in loc) - if(AM != src) - AM.Crossed(src) - if(is_new_area && is_destination_turf) - destination.loc.Entered(src, origin) + . = doMove(destination) + else + CRASH("No valid destination passed into forceMove") - Moved(origin) - return 1 +/atom/movable/proc/moveToNullspace() + return doMove(null) + +/atom/movable/proc/doMove(atom/destination) + . = FALSE + if(destination) + if(pulledby) + pulledby.stop_pulling() + var/atom/oldloc = loc + var/same_loc = oldloc == destination + var/area/old_area = get_area(oldloc) + var/area/destarea = get_area(destination) + + loc = destination + moving_diagonally = 0 + + if(!same_loc) + if(oldloc) + oldloc.Exited(src, destination) + if(old_area && old_area != destarea) + old_area.Exited(src, destination) + for(var/atom/movable/AM in oldloc) + AM.Uncrossed(src) + var/turf/oldturf = get_turf(oldloc) + var/turf/destturf = get_turf(destination) + var/old_z = (oldturf ? oldturf.z : null) + var/dest_z = (destturf ? destturf.z : null) + if (old_z != dest_z) + onTransitZ(old_z, dest_z) + destination.Entered(src, oldloc) + if(destarea && old_area != destarea) + destarea.Entered(src, oldloc) + + for(var/atom/movable/AM in destination) + if(AM == src) + continue + AM.Crossed(src, oldloc) + + Moved(oldloc, NONE, TRUE) + . = TRUE + + //If no destination, move the atom into nullspace (don't do this unless you know what you're doing) + else + . = TRUE + if (loc) + var/atom/oldloc = loc + var/area/old_area = get_area(oldloc) + oldloc.Exited(src, null) + if(old_area) + old_area.Exited(src, null) + loc = null + +/atom/movable/proc/onTransitZ(old_z,new_z) + GLOB.z_moved_event.raise_event(src, old_z, new_z) + for(var/item in src) // Notify contents of Z-transition. This can be overridden IF we know the items contents do not care. + var/atom/movable/AM = item + AM.onTransitZ(old_z,new_z) +///////////////////////////////////////////////////////////////// //called when src is thrown into hit_atom /atom/movable/proc/throw_impact(atom/hit_atom, var/speed) @@ -295,15 +475,12 @@ /atom/movable/proc/adjust_rotation(new_rotation) icon_rotation = new_rotation +<<<<<<< HEAD + update_transform() +======= update_transform() // Called when touching a lava tile. /atom/movable/proc/lava_act() fire_act(null, 10000, 1000) - -// Called when something changes z-levels. -/atom/movable/proc/on_z_change(old_z, new_z) - GLOB.z_moved_event.raise_event(src, old_z, new_z) - for(var/item in src) // Notify contents of Z-transition. This can be overriden IF we know the items contents do not care. - var/atom/movable/AM = item - AM.on_z_change(old_z, new_z) +>>>>>>> 9ff8103... Merge pull request #5636 from kevinz000/pixel_projectiles diff --git a/code/game/gamemodes/cult/construct_spells.dm b/code/game/gamemodes/cult/construct_spells.dm index 60a05e2e27..937284629b 100644 --- a/code/game/gamemodes/cult/construct_spells.dm +++ b/code/game/gamemodes/cult/construct_spells.dm @@ -532,7 +532,8 @@ proc/findNullRod(var/atom/target) /obj/item/weapon/spell/construct/projectile/on_ranged_cast(atom/hit_atom, mob/living/user) if(set_up(hit_atom, user)) var/obj/item/projectile/new_projectile = make_projectile(spell_projectile, user) - new_projectile.launch(hit_atom) + new_projectile.old_style_target(hit_atom) + new_projectile.fire() log_and_message_admins("has casted [src] at \the [hit_atom].") if(fire_sound) playsound(get_turf(src), fire_sound, 75, 1) @@ -597,9 +598,9 @@ proc/findNullRod(var/atom/target) light_power = -2 light_color = "#FFFFFF" - muzzle_type = /obj/effect/projectile/inversion/muzzle - tracer_type = /obj/effect/projectile/inversion/tracer - impact_type = /obj/effect/projectile/inversion/impact + muzzle_type = /obj/effect/projectile/muzzle/inversion + tracer_type = /obj/effect/projectile/tracer/inversion + impact_type = /obj/effect/projectile/impact/inversion //Harvester Pain Orb diff --git a/code/game/gamemodes/technomancer/devices/tesla_armor.dm b/code/game/gamemodes/technomancer/devices/tesla_armor.dm index 56f2085850..86d6f82b08 100644 --- a/code/game/gamemodes/technomancer/devices/tesla_armor.dm +++ b/code/game/gamemodes/technomancer/devices/tesla_armor.dm @@ -75,9 +75,10 @@ H.update_action_buttons() ..() -/obj/item/clothing/suit/armor/tesla/proc/shoot_lightning(var/mob/target, var/power) - var/obj/item/projectile/beam/lightning/lightning = new(src) +/obj/item/clothing/suit/armor/tesla/proc/shoot_lightning(mob/target, power) + var/obj/item/projectile/beam/lightning/lightning = new(get_turf(src)) lightning.power = power - lightning.launch(target) + lightning.old_style_target(target) + lightning.fire() visible_message("\The [src] strikes \the [target] with lightning!") playsound(get_turf(src), 'sound/weapons/gauss_shoot.ogg', 75, 1) \ No newline at end of file diff --git a/code/game/gamemodes/technomancer/spells/energy_siphon.dm b/code/game/gamemodes/technomancer/spells/energy_siphon.dm index 7855014890..aab144d907 100644 --- a/code/game/gamemodes/technomancer/spells/energy_siphon.dm +++ b/code/game/gamemodes/technomancer/spells/energy_siphon.dm @@ -165,14 +165,15 @@ while(i) var/obj/item/projectile/beam/lightning/energy_siphon/lightning = new(get_turf(source)) lightning.firer = user - lightning.launch(user) + lightning.old_style_target(user) + lightning.fire() i-- sleep(3) /obj/item/projectile/beam/lightning/energy_siphon name = "energy stream" icon_state = "lightning" - kill_count = 6 // Backup plan in-case the effect somehow misses the Technomancer. + range = 6 // Backup plan in-case the effect somehow misses the Technomancer. power = 5 // This fires really fast, so this may add up if someone keeps standing in the beam. penetrating = 5 diff --git a/code/game/gamemodes/technomancer/spells/projectile/beam.dm b/code/game/gamemodes/technomancer/spells/projectile/beam.dm index 6eb5e03be3..b96f061df8 100644 --- a/code/game/gamemodes/technomancer/spells/projectile/beam.dm +++ b/code/game/gamemodes/technomancer/spells/projectile/beam.dm @@ -22,6 +22,6 @@ /obj/item/projectile/beam/blue damage = 30 - muzzle_type = /obj/effect/projectile/laser_blue/muzzle - tracer_type = /obj/effect/projectile/laser_blue/tracer - impact_type = /obj/effect/projectile/laser_blue/impact + muzzle_type = /obj/effect/projectile/muzzle/laser_blue + tracer_type = /obj/effect/projectile/tracer/laser_blue + impact_type = /obj/effect/projectile/impact/laser_blue diff --git a/code/game/gamemodes/technomancer/spells/projectile/chain_lightning.dm b/code/game/gamemodes/technomancer/spells/projectile/chain_lightning.dm index 5614d04a88..2d9a59f144 100644 --- a/code/game/gamemodes/technomancer/spells/projectile/chain_lightning.dm +++ b/code/game/gamemodes/technomancer/spells/projectile/chain_lightning.dm @@ -26,9 +26,9 @@ nodamage = 1 damage_type = HALLOSS - muzzle_type = /obj/effect/projectile/lightning/muzzle - tracer_type = /obj/effect/projectile/lightning/tracer - impact_type = /obj/effect/projectile/lightning/impact + muzzle_type = /obj/effect/projectile/muzzle/lightning + tracer_type = /obj/effect/projectile/tracer/lightning + impact_type = /obj/effect/projectile/impact/lightning var/bounces = 3 //How many times it 'chains'. Note that the first hit is not counted as it counts /bounces/. var/list/hit_mobs = list() //Mobs which were already hit. diff --git a/code/game/gamemodes/technomancer/spells/projectile/lightning.dm b/code/game/gamemodes/technomancer/spells/projectile/lightning.dm index e996416216..57e42cf863 100644 --- a/code/game/gamemodes/technomancer/spells/projectile/lightning.dm +++ b/code/game/gamemodes/technomancer/spells/projectile/lightning.dm @@ -26,9 +26,9 @@ nodamage = 1 damage_type = HALLOSS - muzzle_type = /obj/effect/projectile/lightning/muzzle - tracer_type = /obj/effect/projectile/lightning/tracer - impact_type = /obj/effect/projectile/lightning/impact + muzzle_type = /obj/effect/projectile/muzzle/lightning + tracer_type = /obj/effect/projectile/tracer/lightning + impact_type = /obj/effect/projectile/impact/lightning var/power = 60 //How hard it will hit for with electrocute_act(). diff --git a/code/game/gamemodes/technomancer/spells/projectile/projectile.dm b/code/game/gamemodes/technomancer/spells/projectile/projectile.dm index 544213f957..a52bb2e584 100644 --- a/code/game/gamemodes/technomancer/spells/projectile/projectile.dm +++ b/code/game/gamemodes/technomancer/spells/projectile/projectile.dm @@ -12,7 +12,8 @@ /obj/item/weapon/spell/projectile/on_ranged_cast(atom/hit_atom, mob/living/user) if(set_up(hit_atom, user)) var/obj/item/projectile/new_projectile = make_projectile(spell_projectile, user) - new_projectile.launch(hit_atom) + new_projectile.old_style_target(hit_atom) + new_projectile.fire() log_and_message_admins("has casted [src] at \the [hit_atom].") if(fire_sound) playsound(get_turf(src), fire_sound, 75, 1) diff --git a/code/game/machinery/doors/door.dm b/code/game/machinery/doors/door.dm index 01af732f06..b0dde10ee4 100644 --- a/code/game/machinery/doors/door.dm +++ b/code/game/machinery/doors/door.dm @@ -98,6 +98,7 @@ return 1 /obj/machinery/door/Bumped(atom/AM) + . = ..() if(p_open || operating) return if(ismob(AM)) @@ -133,9 +134,6 @@ open() else do_animate("deny") - return - return - /obj/machinery/door/CanPass(atom/movable/mover, turf/target, height=0, air_group=0) if(air_group) return !block_air_zones diff --git a/code/game/machinery/doors/windowdoor.dm b/code/game/machinery/doors/windowdoor.dm index fe21197dc5..1724b49192 100644 --- a/code/game/machinery/doors/windowdoor.dm +++ b/code/game/machinery/doors/windowdoor.dm @@ -68,33 +68,26 @@ if(istype(bot)) if(density && src.check_access(bot.botcard)) open() - sleep(50) - close() + addtimer(CALLBACK(src, .proc/close), 50) else if(istype(AM, /obj/mecha)) var/obj/mecha/mecha = AM if(density) if(mecha.occupant && src.allowed(mecha.occupant)) open() - sleep(50) - close() + addtimer(CALLBACK(src, .proc/close), 50) return if (!( ticker )) return if (src.operating) return - if (src.density && src.allowed(AM)) + if (density && allowed(AM)) open() - if(src.check_access(null)) - sleep(50) - else //secure doors close faster - sleep(20) - close() - return + addtimer(CALLBACK(src, .proc/close), check_access(null)? 50 : 20) -/obj/machinery/door/window/CanPass(atom/movable/mover, turf/target, height=0, air_group=0) +/obj/machinery/door/window/CanPass(atom/movable/mover, turf/target, height, air_group) if(istype(mover) && mover.checkpass(PASSGLASS)) return 1 - if(get_dir(loc, target) == dir) //Make sure looking at appropriate border + if(get_dir(mover, loc) == turn(dir, 180)) //Make sure looking at appropriate border if(air_group) return 0 return !density else @@ -109,7 +102,7 @@ return 1 /obj/machinery/door/window/open() - if (operating == 1) //doors can still open when emag-disabled + if (operating == 1 || !density) //doors can still open when emag-disabled return 0 if (!ticker) return 0 @@ -129,20 +122,20 @@ return 1 /obj/machinery/door/window/close() - if (operating) - return 0 - src.operating = 1 + if(operating || density) + return FALSE + operating = TRUE flick(text("[]closing", src.base_state), src) playsound(src.loc, 'sound/machines/windowdoor.ogg', 100, 1) - density = 1 + density = TRUE update_icon() explosion_resistance = initial(explosion_resistance) update_nearby_tiles() sleep(10) - operating = 0 - return 1 + operating = FALSE + return TRUE /obj/machinery/door/window/take_damage(var/damage) src.health = max(0, src.health - damage) diff --git a/code/game/machinery/embedded_controller/embedded_program_base.dm b/code/game/machinery/embedded_controller/embedded_program_base.dm index a8be295ccd..f170d0a062 100644 --- a/code/game/machinery/embedded_controller/embedded_program_base.dm +++ b/code/game/machinery/embedded_controller/embedded_program_base.dm @@ -17,9 +17,12 @@ /datum/computer/file/embedded_program/proc/receive_signal(datum/signal/signal, receive_method, receive_param) return -/datum/computer/file/embedded_program/process() +<<<<<<< HEAD +/datum/computer/file/embedded_program/proc/process() return +======= +>>>>>>> 9ff8103... Merge pull request #5636 from kevinz000/pixel_projectiles /datum/computer/file/embedded_program/proc/post_signal(datum/signal/signal, comm_line) if(master) master.post_signal(signal, comm_line) diff --git a/code/game/machinery/portable_turret.dm b/code/game/machinery/portable_turret.dm index d300bb6b83..086ab1e182 100644 --- a/code/game/machinery/portable_turret.dm +++ b/code/game/machinery/portable_turret.dm @@ -722,7 +722,10 @@ var/list/turret_icons def_zone = pick(BP_TORSO, BP_GROIN) //Shooting Code: - A.launch(target, def_zone) + A.firer = src + A.old_style_target(target) + A.def_zone = def_zone + A.fire() // Reset the time needed to go back down, since we just tried to shoot at someone. timeout = 10 diff --git a/code/game/mecha/equipment/weapons/weapons.dm b/code/game/mecha/equipment/weapons/weapons.dm index 54e844ef99..2be7566822 100644 --- a/code/game/mecha/equipment/weapons/weapons.dm +++ b/code/game/mecha/equipment/weapons/weapons.dm @@ -19,7 +19,7 @@ return 0 return ..() -/obj/item/mecha_parts/mecha_equipment/weapon/action(atom/target) +/obj/item/mecha_parts/mecha_equipment/weapon/action(atom/target, params) if(!action_checks(target)) return var/turf/curloc = chassis.loc @@ -39,7 +39,7 @@ playsound(chassis, fire_sound, fire_volume, 1) projectiles-- var/P = new projectile(curloc) - Fire(P, target) + Fire(P, target, params) if(i == 1) set_ready_state(0) if(fire_cooldown) @@ -60,11 +60,12 @@ return -/obj/item/mecha_parts/mecha_equipment/weapon/proc/Fire(atom/A, atom/target) +/obj/item/mecha_parts/mecha_equipment/weapon/proc/Fire(atom/A, atom/target, params) var/obj/item/projectile/P = A P.dispersion = deviation process_accuracy(P, chassis.occupant, target) - P.launch(target) + P.preparePixelProjectile(target, chassis.occupant, params) + P.fire() /obj/item/mecha_parts/mecha_equipment/weapon/proc/process_accuracy(obj/projectile, mob/living/user, atom/target) var/obj/item/projectile/P = projectile diff --git a/code/game/mecha/mecha.dm b/code/game/mecha/mecha.dm index 2605e7e0eb..9b17cb8613 100644 --- a/code/game/mecha/mecha.dm +++ b/code/game/mecha/mecha.dm @@ -317,7 +317,7 @@ // return ..() */ -/obj/mecha/proc/click_action(atom/target,mob/user) +/obj/mecha/proc/click_action(atom/target,mob/user, params) if(!src.occupant || src.occupant != user ) return if(user.stat) return if(state) @@ -339,7 +339,7 @@ if(selected && selected.is_ranged()) selected.action(target) else if(selected && selected.is_melee()) - selected.action(target) + selected.action(target, params) else src.melee_action(target) return diff --git a/code/game/objects/buckling.dm b/code/game/objects/buckling.dm index b191c69818..c5ddf057f1 100644 --- a/code/game/objects/buckling.dm +++ b/code/game/objects/buckling.dm @@ -195,6 +195,7 @@ else L.set_dir(dir) return TRUE +<<<<<<< HEAD /atom/movable/Move(atom/newloc, direct = 0) . = ..() @@ -205,3 +206,5 @@ riding_datum.handle_vehicle_layer() riding_datum.handle_vehicle_offsets() //VOREStation Add End +======= +>>>>>>> 9ff8103... Merge pull request #5636 from kevinz000/pixel_projectiles diff --git a/code/game/objects/effects/temporary_visuals/projectiles/impact.dm b/code/game/objects/effects/temporary_visuals/projectiles/impact.dm new file mode 100644 index 0000000000..872c652356 --- /dev/null +++ b/code/game/objects/effects/temporary_visuals/projectiles/impact.dm @@ -0,0 +1,81 @@ +/obj/effect/projectile/impact + name = "beam impact" + icon = 'icons/obj/projectiles_impact.dmi' + +/obj/effect/projectile/impact/laser_pulse + icon_state = "impact_u_laser" + light_range = 2 + light_power = 0.5 + light_color = "#0066FF" + +/obj/effect/projectile/impact/laser_heavy + icon_state = "impact_beam_heavy" + light_range = 3 + light_power = 1 + light_color = "#FF0D00" + +/obj/effect/projectile/impact/xray + icon_state = "impact_xray" + light_range = 2 + light_power = 0.5 + light_color = "#00CC33" + +/obj/effect/projectile/impact/laser_omni + icon_state = "impact_omni" + light_range = 2 + light_power = 0.5 + light_color = "#00C6FF" + +/obj/effect/projectile/impact/laser + icon_state = "impact_laser" + light_range = 2 + light_power = 0.5 + light_color = "#FF0D00" + +/obj/effect/projectile/impact/laser_blue + icon_state = "impact_blue" + light_range = 2 + light_power = 0.5 + light_color = "#0066FF" + +/obj/effect/projectile/impact/emitter + icon_state = "impact_emitter" + light_range = 2 + light_power = 0.5 + light_color = "#00CC33" + +/obj/effect/projectile/impact/stun + icon_state = "impact_stun" + light_range = 2 + light_power = 0.5 + light_color = "#FFFFFF" + +/obj/effect/projectile/impact/lightning + icon_state = "impact_lightning" + light_range = 2 + light_power = 0.5 + light_color = "#00C6FF" + +/obj/effect/projectile/impact/darkmatterstun + icon_state = "impact_darkt" + light_range = 2 + light_power = 0.5 + light_color = "#8837A3" + +/obj/effect/projectile/impact/inversion + icon_state = "impact_invert" + light_range = 2 + light_power = -2 + light_color = "#FFFFFF" + +/obj/effect/projectile/impact/darkmatter + icon_state = "impact_darkb" + light_range = 2 + light_power = 0.5 + light_color = "#8837A3" + +/obj/effect/projectile/tungsten/impact + icon_state = "impact_mhd_laser" + light_range = 4 + light_power = 3 + light_color = "#3300ff" diff --git a/code/game/objects/effects/temporary_visuals/projectiles/muzzle.dm b/code/game/objects/effects/temporary_visuals/projectiles/muzzle.dm new file mode 100644 index 0000000000..42511e6577 --- /dev/null +++ b/code/game/objects/effects/temporary_visuals/projectiles/muzzle.dm @@ -0,0 +1,93 @@ +/obj/effect/projectile/muzzle + name = "muzzle flash" + icon = 'icons/obj/projectiles_muzzle.dmi' + +/obj/effect/projectile/muzzle/emitter + icon_state = "muzzle_emitter" + light_range = 2 + light_power = 0.5 + light_color = "#00CC33" + +/obj/effect/projectile/muzzle/laser_pulse + icon_state = "muzzle_u_laser" + light_range = 2 + light_power = 0.5 + light_color = "#0066FF" + +/obj/effect/projectile/muzzle/pulse + icon_state = "muzzle_pulse" + light_range = 2 + light_power = 0.5 + light_color = "#0066FF" + +/obj/effect/projectile/muzzle/stun + icon_state = "muzzle_stun" + light_range = 2 + light_power = 0.5 + light_color = "#FFFFFF" + +/obj/effect/projectile/muzzle/bullet + icon_state = "muzzle_bullet" + light_range = 2 + light_power = 0.5 + light_color = "#FFFFFF" + +/obj/effect/projectile/muzzle/laser_heavy + icon_state = "muzzle_beam_heavy" + light_range = 3 + light_power = 1 + light_color = "#FF0D00" + +/obj/effect/projectile/muzzle/lightning + icon_state = "muzzle_lightning" + light_range = 2 + light_power = 0.5 + light_color = "#00C6FF" + +/obj/effect/projectile/muzzle/darkmatterstun + icon_state = "muzzle_darkt" + light_range = 2 + light_power = 0.5 + light_color = "#8837A3" + +/obj/effect/projectile/muzzle/laser_blue + icon_state = "muzzle_blue" + light_range = 2 + light_power = 0.5 + light_color = "#0066FF" + +/obj/effect/projectile/muzzle/darkmatter + icon_state = "muzzle_darkb" + light_range = 2 + light_power = 0.5 + light_color = "#8837A3" + +/obj/effect/projectile/muzzle/inversion + icon_state = "muzzle_invert" + light_range = 2 + light_power = -2 + light_color = "#FFFFFF" + +/obj/effect/projectile/muzzle/xray + icon_state = "muzzle_xray" + light_range = 2 + light_power = 0.5 + light_color = "#00CC33" + +/obj/effect/projectile/muzzle/laser_omni + icon_state = "muzzle_omni" + light_range = 2 + light_power = 0.5 + light_color = "#00C6FF" + +/obj/effect/projectile/muzzle/laser + icon_state = "muzzle_laser" + light_range = 2 + light_power = 0.5 + light_color = "#FF0D00" + +/obj/effect/projectile/tungsten/muzzle + icon_state = "muzzle_mhd_laser" + light_range = 4 + light_power = 3 + light_color = "#3300ff" diff --git a/code/game/objects/effects/temporary_visuals/projectiles/projectile_effects.dm b/code/game/objects/effects/temporary_visuals/projectiles/projectile_effects.dm new file mode 100644 index 0000000000..1088a3e4ba --- /dev/null +++ b/code/game/objects/effects/temporary_visuals/projectiles/projectile_effects.dm @@ -0,0 +1,60 @@ +/obj/effect/projectile + name = "pew" + icon = 'icons/obj/projectiles.dmi' + icon_state = "nothing" + layer = ABOVE_MOB_LAYER + anchored = TRUE + mouse_opacity = 0 + appearance_flags = 0 + +/obj/effect/projectile/singularity_pull() + return + +/obj/effect/projectile/singularity_act() + return + +/obj/effect/projectile/proc/scale_to(nx,ny,override=TRUE) + var/matrix/M + if(!override) + M = transform + else + M = new + M.Scale(nx,ny) + transform = M + +/obj/effect/projectile/proc/turn_to(angle,override=TRUE) + var/matrix/M + if(!override) + M = transform + else + M = new + M.Turn(angle) + transform = M + +/obj/effect/projectile/New(angle_override, p_x, p_y, color_override, scaling = 1) + if(angle_override && p_x && p_y && color_override && scaling) + apply_vars(angle_override, p_x, p_y, color_override, scaling) + return ..() + +/obj/effect/projectile/proc/apply_vars(angle_override, p_x = 0, p_y = 0, color_override, scaling = 1, new_loc, increment = 0) + var/mutable_appearance/look = new(src) + look.pixel_x = p_x + look.pixel_y = p_y + if(color_override) + look.color = color_override + appearance = look + scale_to(1,scaling, FALSE) + turn_to(angle_override, FALSE) + if(!isnull(new_loc)) //If you want to null it just delete it... + forceMove(new_loc) + for(var/i in 1 to increment) + pixel_x += round((sin(angle_override)+16*sin(angle_override)*2), 1) + pixel_y += round((cos(angle_override)+16*cos(angle_override)*2), 1) + +/obj/effect/projectile_lighting + var/owner + +/obj/effect/projectile_lighting/New(loc, color, range, intensity, owner_key) + . = ..() + set_light(range, intensity, color) + owner = owner_key diff --git a/code/game/objects/effects/temporary_visuals/projectiles/tracer.dm b/code/game/objects/effects/temporary_visuals/projectiles/tracer.dm new file mode 100644 index 0000000000..54fa41265f --- /dev/null +++ b/code/game/objects/effects/temporary_visuals/projectiles/tracer.dm @@ -0,0 +1,109 @@ +/proc/generate_tracer_between_points(datum/point/starting, datum/point/ending, beam_type, color, qdel_in = 5, light_range = 2, light_color_override, light_intensity = 1, instance_key) //Do not pass z-crossing points as that will not be properly (and likely will never be properly until it's absolutely needed) supported! + if(!istype(starting) || !istype(ending) || !ispath(beam_type)) + return + var/datum/point/midpoint = point_midpoint_points(starting, ending) + var/obj/effect/projectile/tracer/PB = new beam_type + if(isnull(light_color_override)) + light_color_override = color + PB.apply_vars(angle_between_points(starting, ending), midpoint.return_px(), midpoint.return_py(), color, pixel_length_between_points(starting, ending) / world.icon_size, midpoint.return_turf(), 0) + . = PB + if(isnull(light_intensity) && !isnull(PB.light_power)) + light_intensity = PB.light_power + if(isnull(light_range) && !isnull(PB.light_range)) + light_range = PB.light_range + if(isnull(light_color_override) && !isnull(PB.light_color)) + light_color_override = PB.light_color + if(light_range > 0 && light_intensity > 0) + var/list/turf/line = getline(starting.return_turf(), ending.return_turf()) + tracing_line: + for(var/i in line) + var/turf/T = i + for(var/obj/effect/projectile_lighting/PL in T) + if(PL.owner == instance_key) + continue tracing_line + QDEL_IN(new /obj/effect/projectile_lighting(T, light_color_override, light_range, light_intensity, instance_key), qdel_in > 0? qdel_in : 5) + line = null + if(qdel_in) + QDEL_IN(PB, qdel_in) + +/obj/effect/projectile/tracer + name = "beam" + icon = 'icons/obj/projectiles_tracer.dmi' + +/obj/effect/projectile/tracer/stun + icon_state = "stun" + light_range = 2 + light_power = 0.5 + light_color = "#FFFFFF" + +/obj/effect/projectile/tracer/lightning + icon_state = "lightning" + light_range = 2 + light_power = 0.5 + light_color = "#00C6FF" + +/obj/effect/projectile/tracer/laser_pulse + icon_state = "u_laser" + light_range = 2 + light_power = 0.5 + light_color = "#0066FF" + +/obj/effect/projectile/tracer/emitter + icon_state = "emitter" + light_range = 2 + light_power = 0.5 + light_color = "#00CC33" + +/obj/effect/projectile/tracer/darkmatterstun + icon_state = "darkt" + light_range = 2 + light_power = 0.5 + light_color = "#8837A3" + +/obj/effect/projectile/tracer/laser_omni + icon_state = "beam_omni" + light_range = 2 + light_power = 0.5 + light_color = "#00C6FF" + +/obj/effect/projectile/tracer/xray + icon_state = "xray" + light_range = 2 + light_power = 0.5 + light_color = "#00CC33" + +/obj/effect/projectile/tracer/laser_heavy + icon_state = "beam_heavy" + light_range = 3 + light_power = 1 + light_color = "#FF0D00" + +/obj/effect/projectile/tracer/darkmatter + icon_state = "darkb" + light_range = 2 + light_power = 0.5 + light_color = "#8837A3" + +/obj/effect/projectile/tracer/inversion + icon_state = "invert" + light_range = 2 + light_power = -2 + light_color = "#FFFFFF" + +/obj/effect/projectile/tracer/laser + icon_state = "beam" + light_range = 2 + light_power = 0.5 + light_color = "#FF0D00" + +/obj/effect/projectile/tracer/laser_blue + icon_state = "beam_blue" + light_range = 2 + light_power = 0.5 + light_color = "#0066FF" + +/obj/effect/projectile/tungsten/tracer + icon_state = "mhd_laser" + light_range = 4 + light_power = 3 + light_color = "#3300ff" diff --git a/code/game/objects/effects/temporary_visuals/temproary_visual.dm b/code/game/objects/effects/temporary_visuals/temporary_visual.dm similarity index 64% rename from code/game/objects/effects/temporary_visuals/temproary_visual.dm rename to code/game/objects/effects/temporary_visuals/temporary_visual.dm index c5038f1b28..ab8a0ea85b 100644 --- a/code/game/objects/effects/temporary_visuals/temproary_visual.dm +++ b/code/game/objects/effects/temporary_visuals/temporary_visual.dm @@ -7,14 +7,17 @@ mouse_opacity = 0 var/duration = 10 //in deciseconds var/randomdir = TRUE + var/timerid -/obj/effect/temp_visual/Initialize() +/obj/effect/temp_visual/initialize() . = ..() if(randomdir) - set_dir(pick(cardinal)) + dir = pick(list(NORTH, SOUTH, EAST, WEST)) + timerid = QDEL_IN(src, duration) - spawn(duration) - qdel(src) +/obj/effect/temp_visual/Destroy() + . = ..() + deltimer(timerid) /obj/effect/temp_visual/singularity_act() return @@ -25,12 +28,12 @@ /obj/effect/temp_visual/ex_act() return -/* /obj/effect/temp_visual/dir_setting randomdir = FALSE -/obj/effect/temp_visual/dir_setting/Initialize(mapload, set_dir) +/obj/effect/temp_visual/dir_setting/Initialize(loc, set_dir) if(set_dir) - setDir(set_dir) + dir = set_dir . = ..() -*/ //More tg stuff that might be useful later + + diff --git a/code/game/objects/items/weapons/grenades/explosive.dm b/code/game/objects/items/weapons/grenades/explosive.dm index 3b4f6a4511..076a7accce 100644 --- a/code/game/objects/items/weapons/grenades/explosive.dm +++ b/code/game/objects/items/weapons/grenades/explosive.dm @@ -50,9 +50,10 @@ var/fragment_type = pickweight(fragtypes) var/obj/item/projectile/bullet/pellet/fragment/P = new fragment_type(T) P.pellets = fragments_per_projectile - P.shot_from = src.name + P.shot_from = name - P.launch(O) + P.old_style_target(O) + P.fire() //Make sure to hit any mobs in the source turf for(var/mob/living/M in T) diff --git a/code/game/objects/items/weapons/grenades/projectile.dm b/code/game/objects/items/weapons/grenades/projectile.dm index 05153df4fe..8ea02c9add 100644 --- a/code/game/objects/items/weapons/grenades/projectile.dm +++ b/code/game/objects/items/weapons/grenades/projectile.dm @@ -61,7 +61,8 @@ var/obj/item/projectile/P = new shot_type(T) P.shot_from = src.name - P.launch(O) + P.old_style_target(O) + P.fire() //Make sure to hit any mobs in the source turf for(var/mob/living/M in T) diff --git a/code/game/objects/structures/crates_lockers/closets.dm b/code/game/objects/structures/crates_lockers/closets.dm index 7f5db6d116..97f9329b88 100644 --- a/code/game/objects/structures/crates_lockers/closets.dm +++ b/code/game/objects/structures/crates_lockers/closets.dm @@ -69,9 +69,10 @@ else to_chat(user, "It is full.") -/obj/structure/closet/CanPass(atom/movable/mover, turf/target, height=0, air_group=0) - if(air_group || (height==0 || wall_mounted)) return 1 - return (!density) +/obj/structure/closet/CanPass(atom/movable/mover, turf/target, height, air_group) + if(wall_mounted) + return TRUE + return ..() /obj/structure/closet/proc/can_open() if(src.sealed) diff --git a/code/game/objects/structures/window.dm b/code/game/objects/structures/window.dm index 09cc82aa28..a681168f73 100644 --- a/code/game/objects/structures/window.dm +++ b/code/game/objects/structures/window.dm @@ -131,14 +131,20 @@ /obj/structure/window/CanPass(atom/movable/mover, turf/target, height=0, air_group=0) if(istype(mover) && mover.checkpass(PASSGLASS)) +<<<<<<< HEAD return 1 if(is_fulltile()) return 0 //full tile window, you can't move into it! if(get_dir(loc, target) & dir) +======= + return TRUE + if(is_fulltile()) + return FALSE //full tile window, you can't move into it! + if((get_dir(loc, target) & dir) || (get_dir(mover, target) == turn(dir, 180))) +>>>>>>> 9ff8103... Merge pull request #5636 from kevinz000/pixel_projectiles return !density else - return 1 - + return TRUE /obj/structure/window/CheckExit(atom/movable/O as mob|obj, target as turf) if(istype(O) && O.checkpass(PASSGLASS)) @@ -147,7 +153,6 @@ return 0 return 1 - /obj/structure/window/hitby(AM as mob|obj) ..() visible_message("[src] was hit by [AM].") diff --git a/code/game/turfs/turf.dm b/code/game/turfs/turf.dm index f10f88e64d..a8242ad868 100644 --- a/code/game/turfs/turf.dm +++ b/code/game/turfs/turf.dm @@ -142,50 +142,6 @@ turf/attackby(obj/item/weapon/W as obj, mob/user as mob) sleep(2) O.update_transform() -/turf/Enter(atom/movable/mover as mob|obj, atom/forget as mob|obj|turf|area) - if(movement_disabled && usr.ckey != movement_disabled_exception) - usr << "Movement is admin-disabled." //This is to identify lag problems - return - - ..() - - if (!mover || !isturf(mover.loc)) - return 1 - - //First, check objects to block exit that are not on the border - for(var/obj/obstacle in mover.loc) - if(!(obstacle.flags & ON_BORDER) && (mover != obstacle) && (forget != obstacle)) - if(!obstacle.CheckExit(mover, src)) - mover.Bump(obstacle, 1) - return 0 - - //Now, check objects to block exit that are on the border - for(var/obj/border_obstacle in mover.loc) - if((border_obstacle.flags & ON_BORDER) && (mover != border_obstacle) && (forget != border_obstacle)) - if(!border_obstacle.CheckExit(mover, src)) - mover.Bump(border_obstacle, 1) - return 0 - - //Next, check objects to block entry that are on the border - for(var/obj/border_obstacle in src) - if(border_obstacle.flags & ON_BORDER) - if(!border_obstacle.CanPass(mover, mover.loc, 1, 0) && (forget != border_obstacle)) - mover.Bump(border_obstacle, 1) - return 0 - - //Then, check the turf itself - if (!src.CanPass(mover, src)) - mover.Bump(src, 1) - return 0 - - //Finally, check objects/mobs to block entry that are not on the border - for(var/atom/movable/obstacle in src) - if(!(obstacle.flags & ON_BORDER)) - if(!obstacle.CanPass(mover, mover.loc, 1, 0) && (forget != obstacle)) - mover.Bump(obstacle, 1) - return 0 - return 1 //Nothing found to block so return success! - var/const/enterloopsanity = 100 /turf/Entered(atom/atom as mob|obj) @@ -218,14 +174,74 @@ var/const/enterloopsanity = 100 var/objects = 0 if(A && (A.flags & PROXMOVE)) for(var/atom/movable/thing in range(1)) - if(objects > enterloopsanity) break - objects++ + if(objects++ > enterloopsanity) break spawn(0) if(A) //Runtime prevention A.HasProximity(thing, 1) if ((thing && A) && (thing.flags & PROXMOVE)) thing.HasProximity(A, 1) - return + +/turf/CanPass(atom/movable/mover, turf/target) + if(!target) + return FALSE + + if(istype(mover)) // turf/Enter(...) will perform more advanced checks + return !density + + crash_with("Non movable passed to turf CanPass : [mover]") + return FALSE + +//There's a lot of QDELETED() calls here if someone can figure out how to optimize this but not runtime when something gets deleted by a Bump/CanPass/Cross call, lemme know or go ahead and fix this mess - kevinz000 +/turf/Enter(atom/movable/mover, atom/oldloc) + if(movement_disabled && usr.ckey != movement_disabled_exception) + usr << "Movement is admin-disabled." //This is to identify lag problems + return + // Do not call ..() + // Byond's default turf/Enter() doesn't have the behaviour we want with Bump() + // By default byond will call Bump() on the first dense object in contents + // Here's hoping it doesn't stay like this for years before we finish conversion to step_ + var/atom/firstbump + var/CanPassSelf = CanPass(mover, src) + if(CanPassSelf || CHECK_BITFIELD(mover.movement_type, UNSTOPPABLE)) + for(var/i in contents) + if(QDELETED(mover)) + return FALSE //We were deleted, do not attempt to proceed with movement. + if(i == mover || i == mover.loc) // Multi tile objects and moving out of other objects + continue + var/atom/movable/thing = i + if(!thing.Cross(mover)) + if(QDELETED(mover)) //Mover deleted from Cross/CanPass, do not proceed. + return FALSE + if(CHECK_BITFIELD(mover.movement_type, UNSTOPPABLE)) + mover.Bump(thing) + continue + else + if(!firstbump || ((thing.layer > firstbump.layer || thing.flags & ON_BORDER) && !(firstbump.flags & ON_BORDER))) + firstbump = thing + if(QDELETED(mover)) //Mover deleted from Cross/CanPass/Bump, do not proceed. + return FALSE + if(!CanPassSelf) //Even if mover is unstoppable they need to bump us. + firstbump = src + if(firstbump) + mover.Bump(firstbump) + return !QDELETED(mover) && CHECK_BITFIELD(mover.movement_type, UNSTOPPABLE) + return TRUE + +/turf/Exit(atom/movable/mover, atom/newloc) + . = ..() + if(!. || QDELETED(mover)) + return FALSE + for(var/i in contents) + if(i == mover) + continue + var/atom/movable/thing = i + if(!thing.Uncross(mover, newloc)) + if(thing.flags & ON_BORDER) + mover.Bump(thing) + if(!CHECK_BITFIELD(mover.movement_type, UNSTOPPABLE)) + return FALSE + if(QDELETED(mover)) + return FALSE //We were deleted. /turf/proc/adjacent_fire_act(turf/simulated/floor/source, temperature, volume) return diff --git a/code/game/world.dm b/code/game/world.dm new file mode 100644 index 0000000000..4bcc4c8f74 --- /dev/null +++ b/code/game/world.dm @@ -0,0 +1,639 @@ + +#define RECOMMENDED_VERSION 501 +/world/New() + world.log << "Map Loading Complete" + //logs + log_path += time2text(world.realtime, "YYYY/MM-Month/DD-Day/round-hh-mm-ss") + diary = file("[log_path].log") + href_logfile = file("[log_path]-hrefs.htm") + error_log = file("[log_path]-error.log") + debug_log = file("[log_path]-debug.log") + debug_log << "[log_end]\n[log_end]\nStarting up. [time_stamp()][log_end]\n---------------------[log_end]" + changelog_hash = md5('html/changelog.html') //used for telling if the changelog has changed recently + + if(byond_version < RECOMMENDED_VERSION) + world.log << "Your server's byond version does not meet the recommended requirements for this server. Please update BYOND" + + config.post_load() + + if(config && config.server_name != null && config.server_suffix && world.port > 0) + // dumb and hardcoded but I don't care~ + config.server_name += " #[(world.port % 1000) / 100]" + + if(config && config.log_runtime) + log = file("data/logs/runtime/[time2text(world.realtime,"YYYY-MM-DD-(hh-mm-ss)")]-runtime.log") + + GLOB.timezoneOffset = text2num(time2text(0,"hh")) * 36000 + + callHook("startup") + //Emergency Fix + load_mods() + //end-emergency fix + + src.update_status() + + . = ..() + +#if UNIT_TEST + log_unit_test("Unit Tests Enabled. This will destroy the world when testing is complete.") + log_unit_test("If you did not intend to enable this please check code/__defines/unit_testing.dm") +#endif + + // Set up roundstart seed list. + plant_controller = new() + + // This is kinda important. Set up details of what the hell things are made of. + populate_material_list() + + // Create frame types. + populate_frame_types() + + // Create floor types. + populate_flooring_types() + + // Create robolimbs for chargen. + populate_robolimb_list() + + processScheduler = new + master_controller = new /datum/controller/game_controller() + + processScheduler.deferSetupFor(/datum/controller/process/ticker) + processScheduler.setup() + Master.Initialize(10, FALSE) + + spawn(1) + master_controller.setup() +#if UNIT_TEST + initialize_unit_tests() +#endif + + spawn(3000) //so we aren't adding to the round-start lag + if(config.ToRban) + ToRban_autoupdate() + +#undef RECOMMENDED_VERSION + + return + +var/world_topic_spam_protect_ip = "0.0.0.0" +var/world_topic_spam_protect_time = world.timeofday + +/world/Topic(T, addr, master, key) + debug_log << "TOPIC: \"[T]\", from:[addr], master:[master], key:[key][log_end]" + + if (T == "ping") + var/x = 1 + for (var/client/C) + x++ + return x + + else if(T == "players") + var/n = 0 + for(var/mob/M in player_list) + if(M.client) + n++ + return n + + else if (copytext(T,1,7) == "status") + var/input[] = params2list(T) + var/list/s = list() + s["version"] = game_version + s["mode"] = master_mode + s["respawn"] = config.abandon_allowed + s["enter"] = config.enter_allowed + s["vote"] = config.allow_vote_mode + s["ai"] = config.allow_ai + s["host"] = host ? host : null + + // This is dumb, but spacestation13.com's banners break if player count isn't the 8th field of the reply, so... this has to go here. + s["players"] = 0 + s["stationtime"] = stationtime2text() + s["roundduration"] = roundduration2text() + + if(input["status"] == "2") + var/list/players = list() + var/list/admins = list() + + for(var/client/C in GLOB.clients) + if(C.holder) + if(C.holder.fakekey) + continue + admins[C.key] = C.holder.rank + players += C.key + + s["players"] = players.len + s["playerlist"] = list2params(players) + var/list/adm = get_admin_counts() + var/list/presentmins = adm["present"] + var/list/afkmins = adm["afk"] + s["admins"] = presentmins.len + afkmins.len //equivalent to the info gotten from adminwho + s["adminlist"] = list2params(admins) + else + var/n = 0 + var/admins = 0 + + for(var/client/C in GLOB.clients) + if(C.holder) + if(C.holder.fakekey) + continue //so stealthmins aren't revealed by the hub + admins++ + s["player[n]"] = C.key + n++ + + s["players"] = n + s["admins"] = admins + + return list2params(s) + + else if(T == "manifest") + var/list/positions = list() + var/list/set_names = list( + "heads" = command_positions, + "sec" = security_positions, + "eng" = engineering_positions, + "med" = medical_positions, + "sci" = science_positions, + "car" = cargo_positions, + "civ" = civilian_positions, + "bot" = nonhuman_positions + ) + + for(var/datum/data/record/t in data_core.general) + var/name = t.fields["name"] + var/rank = t.fields["rank"] + var/real_rank = make_list_rank(t.fields["real_rank"]) + + var/department = 0 + for(var/k in set_names) + if(real_rank in set_names[k]) + if(!positions[k]) + positions[k] = list() + positions[k][name] = rank + department = 1 + if(!department) + if(!positions["misc"]) + positions["misc"] = list() + positions["misc"][name] = rank + + // Synthetics don't have actual records, so we will pull them from here. + for(var/mob/living/silicon/ai/ai in mob_list) + if(!positions["bot"]) + positions["bot"] = list() + positions["bot"][ai.name] = "Artificial Intelligence" + for(var/mob/living/silicon/robot/robot in mob_list) + // No combat/syndicate cyborgs, no drones. + if(robot.module && robot.module.hide_on_manifest) + continue + if(!positions["bot"]) + positions["bot"] = list() + positions["bot"][robot.name] = "[robot.modtype] [robot.braintype]" + + for(var/k in positions) + positions[k] = list2params(positions[k]) // converts positions["heads"] = list("Bob"="Captain", "Bill"="CMO") into positions["heads"] = "Bob=Captain&Bill=CMO" + + return list2params(positions) + + else if(T == "revision") + if(revdata.revision) + return list2params(list(branch = revdata.branch, date = revdata.date, revision = revdata.revision)) + else + return "unknown" + + else if(copytext(T,1,5) == "info") + var/input[] = params2list(T) + if(input["key"] != config.comms_password) + if(world_topic_spam_protect_ip == addr && abs(world_topic_spam_protect_time - world.time) < 50) + + spawn(50) + world_topic_spam_protect_time = world.time + return "Bad Key (Throttled)" + + world_topic_spam_protect_time = world.time + world_topic_spam_protect_ip = addr + + return "Bad Key" + + var/list/search = params2list(input["info"]) + var/list/ckeysearch = list() + for(var/text in search) + ckeysearch += ckey(text) + + var/list/match = list() + + for(var/mob/M in mob_list) + var/strings = list(M.name, M.ckey) + if(M.mind) + strings += M.mind.assigned_role + strings += M.mind.special_role + for(var/text in strings) + if(ckey(text) in ckeysearch) + match[M] += 10 // an exact match is far better than a partial one + else + for(var/searchstr in search) + if(findtext(text, searchstr)) + match[M] += 1 + + var/maxstrength = 0 + for(var/mob/M in match) + maxstrength = max(match[M], maxstrength) + for(var/mob/M in match) + if(match[M] < maxstrength) + match -= M + + if(!match.len) + return "No matches" + else if(match.len == 1) + var/mob/M = match[1] + var/info = list() + info["key"] = M.key + info["name"] = M.name == M.real_name ? M.name : "[M.name] ([M.real_name])" + info["role"] = M.mind ? (M.mind.assigned_role ? M.mind.assigned_role : "No role") : "No mind" + var/turf/MT = get_turf(M) + info["loc"] = M.loc ? "[M.loc]" : "null" + info["turf"] = MT ? "[MT] @ [MT.x], [MT.y], [MT.z]" : "null" + info["area"] = MT ? "[MT.loc]" : "null" + info["antag"] = M.mind ? (M.mind.special_role ? M.mind.special_role : "Not antag") : "No mind" + info["hasbeenrev"] = M.mind ? M.mind.has_been_rev : "No mind" + info["stat"] = M.stat + info["type"] = M.type + if(isliving(M)) + var/mob/living/L = M + info["damage"] = list2params(list( + oxy = L.getOxyLoss(), + tox = L.getToxLoss(), + fire = L.getFireLoss(), + brute = L.getBruteLoss(), + clone = L.getCloneLoss(), + brain = L.getBrainLoss() + )) + else + info["damage"] = "non-living" + info["gender"] = M.gender + return list2params(info) + else + var/list/ret = list() + for(var/mob/M in match) + ret[M.key] = M.name + return list2params(ret) + + else if(copytext(T,1,9) == "adminmsg") + /* + We got an adminmsg from IRC bot lets split the input then validate the input. + expected output: + 1. adminmsg = ckey of person the message is to + 2. msg = contents of message, parems2list requires + 3. validatationkey = the key the bot has, it should match the gameservers commspassword in it's configuration. + 4. sender = the ircnick that send the message. + */ + + + var/input[] = params2list(T) + if(input["key"] != config.comms_password) + if(world_topic_spam_protect_ip == addr && abs(world_topic_spam_protect_time - world.time) < 50) + + spawn(50) + world_topic_spam_protect_time = world.time + return "Bad Key (Throttled)" + + world_topic_spam_protect_time = world.time + world_topic_spam_protect_ip = addr + + return "Bad Key" + + var/client/C + var/req_ckey = ckey(input["adminmsg"]) + + for(var/client/K in GLOB.clients) + if(K.ckey == req_ckey) + C = K + break + if(!C) + return "No client with that name on server" + + var/rank = input["rank"] + if(!rank) + rank = "Admin" + + var/message = "IRC-[rank] PM from IRC-[input["sender"]]: [input["msg"]]" + var/amessage = "IRC-[rank] PM from IRC-[input["sender"]] to [key_name(C)] : [input["msg"]]" + + C.received_irc_pm = world.time + C.irc_admin = input["sender"] + + C << 'sound/effects/adminhelp.ogg' + C << message + + + for(var/client/A in admins) + if(A != C) + A << amessage + + return "Message Successful" + + else if(copytext(T,1,6) == "notes") + /* + We got a request for notes from the IRC Bot + expected output: + 1. notes = ckey of person the notes lookup is for + 2. validationkey = the key the bot has, it should match the gameservers commspassword in it's configuration. + */ + var/input[] = params2list(T) + if(input["key"] != config.comms_password) + if(world_topic_spam_protect_ip == addr && abs(world_topic_spam_protect_time - world.time) < 50) + + spawn(50) + world_topic_spam_protect_time = world.time + return "Bad Key (Throttled)" + + world_topic_spam_protect_time = world.time + world_topic_spam_protect_ip = addr + return "Bad Key" + + return show_player_info_irc(ckey(input["notes"])) + + else if(copytext(T,1,4) == "age") + var/input[] = params2list(T) + if(input["key"] != config.comms_password) + if(world_topic_spam_protect_ip == addr && abs(world_topic_spam_protect_time - world.time) < 50) + spawn(50) + world_topic_spam_protect_time = world.time + return "Bad Key (Throttled)" + + world_topic_spam_protect_time = world.time + world_topic_spam_protect_ip = addr + return "Bad Key" + + var/age = get_player_age(input["age"]) + if(isnum(age)) + if(age >= 0) + return "[age]" + else + return "Ckey not found" + else + return "Database connection failed or not set up" + + +/world/Reboot(reason = 0, fast_track = FALSE) + /*spawn(0) + world << sound(pick('sound/AI/newroundsexy.ogg','sound/misc/apcdestroyed.ogg','sound/misc/bangindonk.ogg')) // random end sounds!! - LastyBatsy + */ + if (reason || fast_track) //special reboot, do none of the normal stuff + if (usr) + log_admin("[key_name(usr)] Has requested an immediate world restart via client side debugging tools") + message_admins("[key_name_admin(usr)] Has requested an immediate world restart via client side debugging tools") + world << "[key_name_admin(usr)] has requested an immediate world restart via client side debugging tools" + + else + world << "Rebooting world immediately due to host request" + else + processScheduler.stop() + Master.Shutdown() //run SS shutdowns + for(var/client/C in GLOB.clients) + if(config.server) //if you set a server location in config.txt, it sends you there instead of trying to reconnect to the same world address. -- NeoFite + C << link("byond://[config.server]") + + log_world("World rebooted at [time_stamp()]") + ..() + +/hook/startup/proc/loadMode() + world.load_mode() + return 1 + +/world/proc/load_mode() + if(!fexists("data/mode.txt")) + return + + + var/list/Lines = file2list("data/mode.txt") + if(Lines.len) + if(Lines[1]) + master_mode = Lines[1] + log_misc("Saved mode is '[master_mode]'") + +/world/proc/save_mode(var/the_mode) + var/F = file("data/mode.txt") + fdel(F) + F << the_mode + + +/hook/startup/proc/loadMOTD() + world.load_motd() + return 1 + +/world/proc/load_motd() + join_motd = file2text("config/motd.txt") + + +/proc/load_configuration() + config = new /datum/configuration() + config.load("config/config.txt") + config.load("config/game_options.txt","game_options") + config.loadsql("config/dbconfig.txt") + config.loadforumsql("config/forumdbconfig.txt") + +/hook/startup/proc/loadMods() + world.load_mods() + world.load_mentors() // no need to write another hook. + return 1 + +/world/proc/load_mods() + if(config.admin_legacy_system) + var/text = file2text("config/moderators.txt") + if (!text) + error("Failed to load config/mods.txt") + else + var/list/lines = splittext(text, "\n") + for(var/line in lines) + if (!line) + continue + + if (copytext(line, 1, 2) == ";") + continue + + var/title = "Moderator" + var/rights = admin_ranks[title] + + var/ckey = copytext(line, 1, length(line)+1) + var/datum/admins/D = new /datum/admins(title, rights, ckey) + D.associate(GLOB.directory[ckey]) + +/world/proc/load_mentors() + if(config.admin_legacy_system) + var/text = file2text("config/mentors.txt") + if (!text) + error("Failed to load config/mentors.txt") + else + var/list/lines = splittext(text, "\n") + for(var/line in lines) + if (!line) + continue + if (copytext(line, 1, 2) == ";") + continue + + var/title = "Mentor" + var/rights = admin_ranks[title] + + var/ckey = copytext(line, 1, length(line)+1) + var/datum/admins/D = new /datum/admins(title, rights, ckey) + D.associate(GLOB.directory[ckey]) + +/world/proc/update_status() + var/s = "" + + if (config && config.server_name) + s += "[config.server_name] — " + + s += "[station_name()]"; + s += " (" + s += "" //Change this to wherever you want the hub to link to. +// s += "[game_version]" + s += "Default" //Replace this with something else. Or ever better, delete it and uncomment the game version. + s += "" + s += ")" + + var/list/features = list() + + if(ticker) + if(master_mode) + features += master_mode + else + features += "STARTING" + + if (!config.enter_allowed) + features += "closed" + + features += config.abandon_allowed ? "respawn" : "no respawn" + + if (config && config.allow_vote_mode) + features += "vote" + + if (config && config.allow_ai) + features += "AI allowed" + + var/n = 0 + for (var/mob/M in player_list) + if (M.client) + n++ + + if (n > 1) + features += "~[n] players" + else if (n > 0) + features += "~[n] player" + + + if (config && config.hostedby) + features += "hosted by [config.hostedby]" + + if (features) + s += ": [jointext(features, ", ")]" + + /* does this help? I do not know */ + if (src.status != s) + src.status = s + +#define FAILED_DB_CONNECTION_CUTOFF 5 +var/failed_db_connections = 0 +var/failed_old_db_connections = 0 + +/hook/startup/proc/connectDB() + if(!config.sql_enabled) + world.log << "SQL connection disabled in config." + else if(!setup_database_connection()) + world.log << "Your server failed to establish a connection with the feedback database." + else + world.log << "Feedback database connection established." + return 1 + +proc/setup_database_connection() + + if(failed_db_connections > FAILED_DB_CONNECTION_CUTOFF) //If it failed to establish a connection more than 5 times in a row, don't bother attempting to conenct anymore. + return 0 + + if(!dbcon) + dbcon = new() + + var/user = sqlfdbklogin + var/pass = sqlfdbkpass + var/db = sqlfdbkdb + var/address = sqladdress + var/port = sqlport + + dbcon.Connect("dbi:mysql:[db]:[address]:[port]","[user]","[pass]") + . = dbcon.IsConnected() + if ( . ) + failed_db_connections = 0 //If this connection succeeded, reset the failed connections counter. + else + failed_db_connections++ //If it failed, increase the failed connections counter. + world.log << dbcon.ErrorMsg() + + return . + +//This proc ensures that the connection to the feedback database (global variable dbcon) is established +proc/establish_db_connection() + if(failed_db_connections > FAILED_DB_CONNECTION_CUTOFF) + return 0 + + if(!dbcon || !dbcon.IsConnected()) + return setup_database_connection() + else + return 1 + + +/hook/startup/proc/connectOldDB() + if(!config.sql_enabled) + world.log << "SQL connection disabled in config." + else if(!setup_old_database_connection()) + world.log << "Your server failed to establish a connection with the SQL database." + else + world.log << "SQL database connection established." + return 1 + +//These two procs are for the old database, while it's being phased out. See the tgstation.sql file in the SQL folder for more information. +proc/setup_old_database_connection() + + if(failed_old_db_connections > FAILED_DB_CONNECTION_CUTOFF) //If it failed to establish a connection more than 5 times in a row, don't bother attempting to conenct anymore. + return 0 + + if(!dbcon_old) + dbcon_old = new() + + var/user = sqllogin + var/pass = sqlpass + var/db = sqldb + var/address = sqladdress + var/port = sqlport + + dbcon_old.Connect("dbi:mysql:[db]:[address]:[port]","[user]","[pass]") + . = dbcon_old.IsConnected() + if ( . ) + failed_old_db_connections = 0 //If this connection succeeded, reset the failed connections counter. + else + failed_old_db_connections++ //If it failed, increase the failed connections counter. + world.log << dbcon.ErrorMsg() + + return . + +//This proc ensures that the connection to the feedback database (global variable dbcon) is established +proc/establish_old_db_connection() + if(failed_old_db_connections > FAILED_DB_CONNECTION_CUTOFF) + return 0 + + if(!dbcon_old || !dbcon_old.IsConnected()) + return setup_old_database_connection() + else + return 1 + +// Things to do when a new z-level was just made. +/world/proc/max_z_changed() + if(!islist(GLOB.players_by_zlevel)) + GLOB.players_by_zlevel = new /list(world.maxz, 0) + while(GLOB.players_by_zlevel.len < world.maxz) + GLOB.players_by_zlevel.len++ + GLOB.players_by_zlevel[GLOB.players_by_zlevel.len] = list() + +// Call this to make a new blank z-level, don't modify maxz directly. +/world/proc/increment_max_z() + maxz++ + max_z_changed() + +#undef FAILED_DB_CONNECTION_CUTOFF diff --git a/code/global_init.dm b/code/global_init.dm new file mode 100644 index 0000000000..c562209183 --- /dev/null +++ b/code/global_init.dm @@ -0,0 +1,27 @@ +/* + The initialization of the game happens roughly like this: + + 1. All global variables are initialized (including the global_init instance). + 2. The map is initialized, and map objects are created. + 3. world/New() runs, creating the process scheduler (and the old master controller) and spawning their setup. + 4. processScheduler/setup() runs, creating all the processes. game_controller/setup() runs, calling initialize() on all movable atoms in the world. + 5. The gameticker is created. + +*/ +var/global/datum/global_init/init = new () + +/* + Pre-map initialization stuff should go here. +*/ +/datum/global_init/New() + + makeDatumRefLists() + load_configuration() + + initialize_integrated_circuits_list() + + qdel(src) //we're done + +/datum/global_init/Destroy() + global.init = null + return 2 // QDEL_HINT_IWILLGC diff --git a/code/modules/clothing/chameleon.dm b/code/modules/clothing/chameleon.dm index d2207e5c68..5d5296db77 100644 --- a/code/modules/clothing/chameleon.dm +++ b/code/modules/clothing/chameleon.dm @@ -432,7 +432,7 @@ P.pass_flags = initial(copy_projectile.pass_flags) P.fire_sound = initial(copy_projectile.fire_sound) P.hitscan = initial(copy_projectile.hitscan) - P.step_delay = initial(copy_projectile.step_delay) + P.speed = initial(copy_projectile.speed) P.muzzle_type = initial(copy_projectile.muzzle_type) P.tracer_type = initial(copy_projectile.tracer_type) P.impact_type = initial(copy_projectile.impact_type) diff --git a/code/modules/mob/living/bot/ed209bot.dm b/code/modules/mob/living/bot/ed209bot.dm index 7c709bc165..b8f1c85485 100644 --- a/code/modules/mob/living/bot/ed209bot.dm +++ b/code/modules/mob/living/bot/ed209bot.dm @@ -68,8 +68,9 @@ playsound(loc, emagged ? 'sound/weapons/Laser.ogg' : 'sound/weapons/Taser.ogg', 50, 1) var/obj/item/projectile/P = new projectile(loc) - P.launch(A) - return + P.firer = src + P.old_style_target(A) + P.fire() // Assembly diff --git a/code/modules/mob/living/carbon/carbon.dm b/code/modules/mob/living/carbon/carbon.dm index 1540c068c7..505821640d 100644 --- a/code/modules/mob/living/carbon/carbon.dm +++ b/code/modules/mob/living/carbon/carbon.dm @@ -348,12 +348,12 @@ if(alert(src,"You sure you want to sleep for a while?","Sleep","Yes","No") == "Yes") usr.sleeping = 20 //Short nap -/mob/living/carbon/Bump(var/atom/movable/AM, yes) - if(now_pushing || !yes) +/mob/living/carbon/Bump(atom/A) + if(now_pushing) return ..() - if(istype(AM, /mob/living/carbon) && prob(10)) - src.spread_disease_to(AM, "Contact") + if(istype(A, /mob/living/carbon) && prob(10)) + spread_disease_to(A, "Contact") /mob/living/carbon/cannot_use_vents() return diff --git a/code/modules/mob/living/carbon/human/species/xenomorphs/alien_powers.dm b/code/modules/mob/living/carbon/human/species/xenomorphs/alien_powers.dm index 0c8ca611fe..cc7cd7ef85 100644 --- a/code/modules/mob/living/carbon/human/species/xenomorphs/alien_powers.dm +++ b/code/modules/mob/living/carbon/human/species/xenomorphs/alien_powers.dm @@ -142,7 +142,8 @@ visible_message("[src] spits [spit_name] at \the [A]!", "You spit [spit_name] at \the [A].") var/obj/item/projectile/P = new spit_projectile(get_turf(src)) P.firer = src - P.launch(A) + P.old_style_target(A) + P.fire() playsound(loc, 'sound/weapons/pierce.ogg', 25, 0) else ..() diff --git a/code/modules/mob/living/living.dm b/code/modules/mob/living/living.dm index 75a291632c..83f30a05df 100644 --- a/code/modules/mob/living/living.dm +++ b/code/modules/mob/living/living.dm @@ -80,49 +80,23 @@ default behaviour is: return 1 return 0 -/mob/living/Bump(atom/movable/AM, yes) - spawn(0) - if ((!( yes ) || now_pushing) || !loc) - return - now_pushing = 1 - if (istype(AM, /mob/living)) - var/mob/living/tmob = AM +/mob/living/Bump(atom/movable/AM) + if(now_pushing || !loc) + return + now_pushing = 1 + if (istype(AM, /mob/living)) + var/mob/living/tmob = AM - //Even if we don't push/swap places, we "touched" them, so spread fire - spread_fire(tmob) + //Even if we don't push/swap places, we "touched" them, so spread fire + spread_fire(tmob) - for(var/mob/living/M in range(tmob, 1)) - if(tmob.pinned.len || ((M.pulling == tmob && ( tmob.restrained() && !( M.restrained() ) && M.stat == 0)) || locate(/obj/item/weapon/grab, tmob.grabbed_by.len)) ) - if ( !(world.time % 5) ) - to_chat(src, "[tmob] is restrained, you cannot push past") - now_pushing = 0 - return - if( tmob.pulling == M && ( M.restrained() && !( tmob.restrained() ) && tmob.stat == 0) ) - if ( !(world.time % 5) ) - to_chat(src, "[tmob] is restraining [M], you cannot push past") - now_pushing = 0 - return - - //BubbleWrap: people in handcuffs are always switched around as if they were on 'help' intent to prevent a person being pulled from being seperated from their puller - var/dense = 0 - if(loc.density) - dense = 1 - for(var/atom/movable/A in loc) - if(A == src) - continue - if(A.density) - if(A.flags&ON_BORDER) - dense = !A.CanPass(src, src.loc) - else - dense = 1 - if(dense) break - - //Leaping mobs just land on the tile, no pushing, no anything. - if(status_flags & LEAPING) - loc = tmob.loc - status_flags &= ~LEAPING + for(var/mob/living/M in range(tmob, 1)) + if(tmob.pinned.len || ((M.pulling == tmob && ( tmob.restrained() && !( M.restrained() ) && M.stat == 0)) || locate(/obj/item/weapon/grab, tmob.grabbed_by.len)) ) + if ( !(world.time % 5) ) + to_chat(src, "[tmob] is restrained, you cannot push past") now_pushing = 0 return +<<<<<<< HEAD if((tmob.mob_always_swap || (tmob.a_intent == I_HELP || tmob.restrained()) && (a_intent == I_HELP || src.restrained())) && tmob.canmove && canmove && !tmob.buckled && !buckled && !dense && can_move_mob(tmob, 1, 0)) // mutual brohugs all around! var/turf/oldloc = loc @@ -145,15 +119,58 @@ default behaviour is: // VOREStation Edit - End tmob.forceMove(oldloc) +======= + if( tmob.pulling == M && ( M.restrained() && !( tmob.restrained() ) && tmob.stat == 0) ) + if ( !(world.time % 5) ) + to_chat(src, "[tmob] is restraining [M], you cannot push past") +>>>>>>> 9ff8103... Merge pull request #5636 from kevinz000/pixel_projectiles now_pushing = 0 return - if(!can_move_mob(tmob, 0, 0)) + //BubbleWrap: people in handcuffs are always switched around as if they were on 'help' intent to prevent a person being pulled from being seperated from their puller + var/dense = 0 + if(loc.density) + dense = 1 + for(var/atom/movable/A in loc) + if(A == src) + continue + if(A.density) + if(A.flags&ON_BORDER) + dense = !A.CanPass(src, src.loc) + else + dense = 1 + if(dense) break + + //Leaping mobs just land on the tile, no pushing, no anything. + if(status_flags & LEAPING) + loc = tmob.loc + status_flags &= ~LEAPING + now_pushing = 0 + return + + if((tmob.mob_always_swap || (tmob.a_intent == I_HELP || tmob.restrained()) && (a_intent == I_HELP || src.restrained())) && tmob.canmove && canmove && !tmob.buckled && !buckled && !dense && can_move_mob(tmob, 1, 0)) // mutual brohugs all around! + var/turf/oldloc = loc + forceMove(tmob.loc) + tmob.forceMove(oldloc) + now_pushing = 0 + return + + if(!can_move_mob(tmob, 0, 0)) + now_pushing = 0 + return + if(a_intent == I_HELP || src.restrained()) + now_pushing = 0 + return + if(istype(tmob, /mob/living/carbon/human) && (FAT in tmob.mutations)) + if(prob(40) && !(FAT in src.mutations)) + to_chat(src, "You fail to push [tmob]'s fat ass out of the way.") now_pushing = 0 return - if(a_intent == I_HELP || src.restrained()) + if(tmob.r_hand && istype(tmob.r_hand, /obj/item/weapon/shield/riot)) + if(prob(99)) now_pushing = 0 return +<<<<<<< HEAD // VOREStation Edit - Begin // Plow that nerd. @@ -182,11 +199,19 @@ default behaviour is: now_pushing = 0 return if(!(tmob.status_flags & CANPUSH)) +======= + if(tmob.l_hand && istype(tmob.l_hand, /obj/item/weapon/shield/riot)) + if(prob(99)) +>>>>>>> 9ff8103... Merge pull request #5636 from kevinz000/pixel_projectiles now_pushing = 0 return + if(!(tmob.status_flags & CANPUSH)) + now_pushing = 0 + return - tmob.LAssailant = src + tmob.LAssailant = src +<<<<<<< HEAD now_pushing = 0 spawn(0) ..() @@ -203,27 +228,36 @@ default behaviour is: src << ("You just [pick("ran", "slammed")] into \the [AM]!") to_chat(src, "You just [pick("ran", "slammed")] into \the [AM]!") */ // VOREStation Removal End +======= + now_pushing = 0 + . = ..() + if (!istype(AM, /atom/movable) || AM.anchored) + if(confused && prob(50) && m_intent=="run") + Weaken(2) + playsound(loc, "punch", 25, 1, -1) + visible_message("[src] [pick("ran", "slammed")] into \the [AM]!") + src.apply_damage(5, BRUTE) + to_chat(src, "You just [pick("ran", "slammed")] into \the [AM]!") + return + if (!now_pushing) + if(isobj(AM)) + var/obj/I = AM + if(!can_pull_size || can_pull_size < I.w_class) +>>>>>>> 9ff8103... Merge pull request #5636 from kevinz000/pixel_projectiles return - if (!now_pushing) - if(isobj(AM)) - var/obj/I = AM - if(!can_pull_size || can_pull_size < I.w_class) - return - now_pushing = 1 + now_pushing = 1 - var/t = get_dir(src, AM) - if (istype(AM, /obj/structure/window)) - for(var/obj/structure/window/win in get_step(AM,t)) - now_pushing = 0 - return - step(AM, t) - if(ishuman(AM) && AM:grabbed_by) - for(var/obj/item/weapon/grab/G in AM:grabbed_by) - step(G:assailant, get_dir(G:assailant, AM)) - G.adjust_position() + var/t = get_dir(src, AM) + if (istype(AM, /obj/structure/window)) + for(var/obj/structure/window/win in get_step(AM,t)) now_pushing = 0 - return - return + return + step(AM, t) + if(ishuman(AM) && AM:grabbed_by) + for(var/obj/item/weapon/grab/G in AM:grabbed_by) + step(G:assailant, get_dir(G:assailant, AM)) + G.adjust_position() + now_pushing = 0 /mob/living/CanPass(atom/movable/mover, turf/target) if(istype(mover, /obj/structure/blob) && faction == "blob") //Blobs should ignore things on their faction. diff --git a/code/modules/mob/living/simple_animal/simple_animal.dm b/code/modules/mob/living/simple_animal/simple_animal.dm index 56c3cd5da9..8bc758d265 100644 --- a/code/modules/mob/living/simple_animal/simple_animal.dm +++ b/code/modules/mob/living/simple_animal/simple_animal.dm @@ -1383,17 +1383,15 @@ if(target == start) return - var/obj/item/projectile/A = new projectiletype(user:loc) + var/obj/item/projectile/A = new projectiletype(user.loc) playsound(user, projectilesound, 100, 1) if(!A) return // if (!istype(target, /turf)) // qdel(A) // return - - A.firer = src - A.launch(target) - return + A.old_style_target(target) + A.fire() //We can't see the target /mob/living/simple_mob/proc/LoseTarget() diff --git a/code/modules/mob/living/simple_mob/combat.dm b/code/modules/mob/living/simple_mob/combat.dm index ae1fa4ad3b..8cb60cd177 100644 --- a/code/modules/mob/living/simple_mob/combat.dm +++ b/code/modules/mob/living/simple_mob/combat.dm @@ -128,7 +128,8 @@ playsound(src, P.fire_sound ? P.fire_sound : projectilesound, 80, 1) P.firer = src // So we can't shoot ourselves. - P.launch(A) + P.old_style_target(A, src) + P.fire() if(needs_reload) reload_count++ diff --git a/code/modules/mob/living/simple_mob/subtypes/animal/giant_spider/webslinger.dm b/code/modules/mob/living/simple_mob/subtypes/animal/giant_spider/webslinger.dm index 0024bc8962..dff65f1e2b 100644 --- a/code/modules/mob/living/simple_mob/subtypes/animal/giant_spider/webslinger.dm +++ b/code/modules/mob/living/simple_mob/subtypes/animal/giant_spider/webslinger.dm @@ -33,5 +33,6 @@ playsound(src, 'sound/weapons/thudswoosh.ogg', 100, 1) if(!B) return - B.launch(A) + B.old_style_target(A, src) + B.fire() set_AI_busy(FALSE) \ No newline at end of file diff --git a/code/modules/mob/living/simple_mob/subtypes/mechanical/mecha/adv_dark_gygax.dm b/code/modules/mob/living/simple_mob/subtypes/mechanical/mecha/adv_dark_gygax.dm index 246b0fa1c6..b58c6f1a04 100644 --- a/code/modules/mob/living/simple_mob/subtypes/mechanical/mecha/adv_dark_gygax.dm +++ b/code/modules/mob/living/simple_mob/subtypes/mechanical/mecha/adv_dark_gygax.dm @@ -1,261 +1,263 @@ -// Stronger than a regular Dark Gygax, this one has three special attacks, based on intents. -// First special attack launches three arcing rockets at the current target. -// Second special attack fires a projectile that creates a short-lived microsingularity that pulls in everything nearby. Magboots can protect from this. -// Third special attack creates a dangerous electric field that causes escalating electric damage, before emitting a tesla shock and blinding anyone looking at the mecha. -// The AI will choose one every ten seconds. -/mob/living/simple_mob/mechanical/mecha/combat/gygax/dark/advanced - name = "advanced dark gygax" - desc = "An experimental exosuit that utilizes advanced materials to allow for greater protection while still being lightweight and fast. \ - It also is armed with an array of next-generation weaponry." - icon_state = "darkgygax_adv" - wreckage = /obj/structure/loot_pile/mecha/gygax/dark/adv - icon_scale = 1.5 - movement_shake_radius = 14 - - maxHealth = 450 - deflect_chance = 25 - has_repair_droid = TRUE - armor = list( - "melee" = 50, - "bullet" = 50, - "laser" = 50, - "energy" = 30, - "bomb" = 30, - "bio" = 100, - "rad" = 100 - ) - - special_attack_min_range = 1 - special_attack_max_range = 7 - special_attack_cooldown = 10 SECONDS - projectiletype = /obj/item/projectile/force_missile - projectilesound = 'sound/weapons/wave.ogg' - var/obj/effect/overlay/energy_ball/energy_ball = null - -/mob/living/simple_mob/mechanical/mecha/combat/gygax/dark/advanced/Destroy() - if(energy_ball) - energy_ball.stop_orbit() - qdel(energy_ball) - return ..() - -/mob/living/simple_mob/mechanical/mecha/combat/gygax/dark/advanced/do_special_attack(atom/A) - . = TRUE // So we don't fire a laser as well. - switch(a_intent) - if(I_DISARM) // Side gun - electric_defense(A) - if(I_HURT) // Rockets - launch_rockets(A) - if(I_GRAB) // Micro-singulo - launch_microsingularity(A) - -#define ELECTRIC_ZAP_POWER 20000 - -// Charges a tesla shot, while emitting a dangerous electric field. The exosuit is immune to electric damage while this is ongoing. -// It also briefly blinds anyone looking directly at the mech without flash protection. -/mob/living/simple_mob/mechanical/mecha/combat/gygax/dark/advanced/proc/electric_defense(atom/target) - set waitfor = FALSE - - // Temporary immunity to shock to avoid killing themselves with their own attack. - var/old_shock_resist = shock_resist - shock_resist = 1 - - // Make the energy ball. This is purely visual since the tesla ball is hyper-deadly. - energy_ball = new(loc) - energy_ball.adjust_scale(0.5) - energy_ball.orbit(src, 32, TRUE, 1 SECOND) - - visible_message(span("warning", "\The [src] creates \an [energy_ball] around itself!")) - - playsound(src.loc, 'sound/effects/lightning_chargeup.ogg', 100, 1, extrarange = 30) - - // Shock nearby things that aren't ourselves. - for(var/i = 1 to 10) - energy_ball.adjust_scale(0.5 + (i/10)) - energy_ball.set_light(i/2, i/2, "#0000FF") - for(var/thing in range(3, src)) - // This is stupid because mechs are stupid and not mobs. - if(isliving(thing)) - var/mob/living/L = thing - - if(L == src) - continue - if(L.stat) - continue // Otherwise it can get pretty laggy if there's loads of corpses around. - L.inflict_shock_damage(i * 2) - if(L && L.has_AI()) // Some mobs delete themselves when dying. - L.ai_holder.react_to_attack(src) - - else if(istype(thing, /obj/mecha)) - var/obj/mecha/M = thing - M.take_damage(i * 2, "energy") // Mechs don't have a concept for siemens so energy armor check is the best alternative. - - sleep(1 SECOND) - - // Shoot a tesla bolt, and flashes people who are looking at the mecha without sufficent eye protection. - visible_message(span("warning", "\The [energy_ball] explodes in a flash of light, sending a shock everywhere!")) - playsound(src.loc, 'sound/effects/lightningbolt.ogg', 100, 1, extrarange = 30) - tesla_zap(src.loc, 5, ELECTRIC_ZAP_POWER, FALSE) - for(var/mob/living/L in viewers(src)) - if(L == src) - continue - var/dir_towards_us = get_dir(L, src) - if(L.dir && L.dir & dir_towards_us) - to_chat(L, span("danger", "The flash of light blinds you briefly.")) - L.flash_eyes(intensity = FLASH_PROTECTION_MODERATE, override_blindness_check = FALSE, affect_silicon = TRUE) - - // Get rid of our energy ball. - energy_ball.stop_orbit() - qdel(energy_ball) - - sleep(1 SECOND) - // Resist resistance to old value. - shock_resist = old_shock_resist // Not using initial() in case the value gets modified by an admin or something. - -#undef ELECTRIC_ZAP_POWER - -/mob/living/simple_mob/mechanical/mecha/combat/gygax/dark/advanced/proc/launch_rockets(atom/target) - set waitfor = FALSE - - // Telegraph our next move. - Beam(target, icon_state = "sat_beam", time = 3.5 SECONDS, maxdistance = INFINITY) - visible_message(span("warning", "\The [src] deploys a missile rack!")) - playsound(src, 'sound/effects/turret/move1.wav', 50, 1) - sleep(0.5 SECONDS) - - for(var/i = 1 to 3) - if(target) // Might get deleted in the meantime. - var/turf/T = get_turf(target) - if(T) - visible_message(span("warning", "\The [src] fires a rocket into the air!")) - playsound(src, 'sound/weapons/rpg.ogg', 70, 1) - face_atom(T) - var/obj/item/projectile/arc/explosive_rocket/rocket = new(loc) - rocket.launch(T) - sleep(1 SECOND) - - visible_message(span("warning", "\The [src] retracts the missile rack.")) - playsound(src, 'sound/effects/turret/move2.wav', 50, 1) - -// Arcing rocket projectile that produces a weak explosion when it lands. -// Shouldn't punch holes in the floor, but will still hurt. -/obj/item/projectile/arc/explosive_rocket - name = "rocket" - icon_state = "mortar" - -/obj/item/projectile/arc/explosive_rocket/on_impact(turf/T) - new /obj/effect/explosion(T) // Weak explosions don't produce this on their own, apparently. - explosion(T, 0, 0, 2, adminlog = FALSE) - -/mob/living/simple_mob/mechanical/mecha/combat/gygax/dark/advanced/proc/launch_microsingularity(atom/target) - var/turf/T = get_turf(target) - visible_message(span("warning", "\The [src] fires an energetic sphere into the air!")) - playsound(src, 'sound/weapons/Laser.ogg', 50, 1) - face_atom(T) - var/obj/item/projectile/arc/microsingulo/sphere = new(loc) - sphere.launch(T) - -/obj/item/projectile/arc/microsingulo - name = "micro singularity" - icon_state = "bluespace" - -/obj/item/projectile/arc/microsingulo/on_impact(turf/T) - new /obj/effect/temporary_effect/pulse/microsingulo(T) - - -/obj/effect/temporary_effect/pulse/microsingulo - name = "micro singularity" - desc = "It's sucking everything in!" - icon = 'icons/obj/objects.dmi' - icon_state = "bhole3" - light_range = 4 - light_power = 5 - light_color = "#2ECCFA" - pulses_remaining = 10 - pulse_delay = 0.5 SECONDS - var/pull_radius = 3 - var/pull_strength = STAGE_THREE - -/obj/effect/temporary_effect/pulse/microsingulo/on_pulse() - for(var/atom/A in range(pull_radius, src)) - A.singularity_pull(src, pull_strength) - - -// The Advanced Dark Gygax's AI. -// The mob has three special attacks, based on the current intent. -// This AI choose the appropiate intent for the situation, and tries to ensure it doesn't kill itself by firing missiles at its feet. -/datum/ai_holder/simple_mob/intentional/adv_dark_gygax - conserve_ammo = TRUE // Might help avoid 'I shoot the wall forever' cheese. - var/closest_desired_distance = 1 // Otherwise run up to them to be able to potentially shock or punch them. - - var/electric_defense_radius = 3 // How big to assume electric defense's area is. - var/microsingulo_radius = 3 // Same but for microsingulo pull. - var/rocket_explosive_radius = 2 // Explosion radius for the rockets. - - var/electric_defense_threshold = 2 // How many non-targeted people are needed in close proximity before electric defense is viable. - var/microsingulo_threshold = 2 // Similar to above, but uses an area around the target. - -// Used to control the mob's positioning based on which special attack it has done. -// Note that the intent will not change again until the next special attack is about to happen. -/datum/ai_holder/simple_mob/intentional/adv_dark_gygax/on_engagement(atom/A) - // Make the AI backpeddle if using an AoE special attack. - var/list/risky_intents = list(I_GRAB, I_HURT) // Mini-singulo and missiles. - if(holder.a_intent in risky_intents) - var/closest_distance = 1 - switch(holder.a_intent) // Plus one just in case. - if(I_HURT) - closest_distance = rocket_explosive_radius + 1 - if(I_GRAB) - closest_distance = microsingulo_radius + 1 - - if(get_dist(holder, A) <= closest_distance) - holder.IMove(get_step_away(holder, A, closest_distance)) - - // Otherwise get up close and personal. - else if(get_dist(holder, A) > closest_desired_distance) - holder.IMove(get_step_towards(holder, A)) - -// Changes the mob's intent, which controls which special attack is used. -// I_DISARM causes Electric Defense, I_GRAB causes Micro-Singularity, and I_HURT causes Missile Barrage. -/datum/ai_holder/simple_mob/intentional/adv_dark_gygax/pre_special_attack(atom/A) - if(isliving(A)) - var/mob/living/target = A - - // If we're surrounded, Electric Defense will quickly fix that. - var/tally = 0 - var/list/potential_targets = list_targets() // Returns list of mobs and certain objects like mechs and turrets. - for(var/atom/movable/AM in potential_targets) - if(get_dist(holder, AM) > electric_defense_radius) - continue - if(!can_attack(AM)) - continue - tally++ - - // Should we shock them? - if(tally >= electric_defense_threshold || get_dist(target, holder) <= electric_defense_radius) - holder.a_intent = I_DISARM - return - - // Otherwise they're a fair distance away and we're not getting mobbed up close. - // See if we should use missiles or microsingulo. - tally = 0 // Let's recycle the var. - for(var/atom/movable/AM in potential_targets) - if(get_dist(target, AM) > microsingulo_radius) // Deliberately tests distance between target and nearby targets and not the holder. - continue - if(!can_attack(AM)) - continue - if(AM.anchored) // Microsingulo doesn't do anything to anchored things. - tally-- - else - tally++ - - // Lots of people means minisingulo would be more useful. - if(tally >= microsingulo_threshold) - holder.a_intent = I_GRAB - else // Otherwise use rockets. - holder.a_intent = I_HURT - - else - if(get_dist(holder, A) >= rocket_explosive_radius + 1) - holder.a_intent = I_HURT // Fire rockets if it's an obj/turf. - else - holder.a_intent = I_DISARM // Electricity might not work but it's safe up close. +// Stronger than a regular Dark Gygax, this one has three special attacks, based on intents. +// First special attack launches three arcing rockets at the current target. +// Second special attack fires a projectile that creates a short-lived microsingularity that pulls in everything nearby. Magboots can protect from this. +// Third special attack creates a dangerous electric field that causes escalating electric damage, before emitting a tesla shock and blinding anyone looking at the mecha. +// The AI will choose one every ten seconds. +/mob/living/simple_mob/mechanical/mecha/combat/gygax/dark/advanced + name = "advanced dark gygax" + desc = "An experimental exosuit that utilizes advanced materials to allow for greater protection while still being lightweight and fast. \ + It also is armed with an array of next-generation weaponry." + icon_state = "darkgygax_adv" + wreckage = /obj/structure/loot_pile/mecha/gygax/dark/adv + icon_scale = 1.5 + movement_shake_radius = 14 + + maxHealth = 450 + deflect_chance = 25 + has_repair_droid = TRUE + armor = list( + "melee" = 50, + "bullet" = 50, + "laser" = 50, + "energy" = 30, + "bomb" = 30, + "bio" = 100, + "rad" = 100 + ) + + special_attack_min_range = 1 + special_attack_max_range = 7 + special_attack_cooldown = 10 SECONDS + projectiletype = /obj/item/projectile/force_missile + projectilesound = 'sound/weapons/wave.ogg' + var/obj/effect/overlay/energy_ball/energy_ball = null + +/mob/living/simple_mob/mechanical/mecha/combat/gygax/dark/advanced/Destroy() + if(energy_ball) + energy_ball.stop_orbit() + qdel(energy_ball) + return ..() + +/mob/living/simple_mob/mechanical/mecha/combat/gygax/dark/advanced/do_special_attack(atom/A) + . = TRUE // So we don't fire a laser as well. + switch(a_intent) + if(I_DISARM) // Side gun + electric_defense(A) + if(I_HURT) // Rockets + launch_rockets(A) + if(I_GRAB) // Micro-singulo + launch_microsingularity(A) + +#define ELECTRIC_ZAP_POWER 20000 + +// Charges a tesla shot, while emitting a dangerous electric field. The exosuit is immune to electric damage while this is ongoing. +// It also briefly blinds anyone looking directly at the mech without flash protection. +/mob/living/simple_mob/mechanical/mecha/combat/gygax/dark/advanced/proc/electric_defense(atom/target) + set waitfor = FALSE + + // Temporary immunity to shock to avoid killing themselves with their own attack. + var/old_shock_resist = shock_resist + shock_resist = 1 + + // Make the energy ball. This is purely visual since the tesla ball is hyper-deadly. + energy_ball = new(loc) + energy_ball.adjust_scale(0.5) + energy_ball.orbit(src, 32, TRUE, 1 SECOND) + + visible_message(span("warning", "\The [src] creates \an [energy_ball] around itself!")) + + playsound(src.loc, 'sound/effects/lightning_chargeup.ogg', 100, 1, extrarange = 30) + + // Shock nearby things that aren't ourselves. + for(var/i = 1 to 10) + energy_ball.adjust_scale(0.5 + (i/10)) + energy_ball.set_light(i/2, i/2, "#0000FF") + for(var/thing in range(3, src)) + // This is stupid because mechs are stupid and not mobs. + if(isliving(thing)) + var/mob/living/L = thing + + if(L == src) + continue + if(L.stat) + continue // Otherwise it can get pretty laggy if there's loads of corpses around. + L.inflict_shock_damage(i * 2) + if(L && L.has_AI()) // Some mobs delete themselves when dying. + L.ai_holder.react_to_attack(src) + + else if(istype(thing, /obj/mecha)) + var/obj/mecha/M = thing + M.take_damage(i * 2, "energy") // Mechs don't have a concept for siemens so energy armor check is the best alternative. + + sleep(1 SECOND) + + // Shoot a tesla bolt, and flashes people who are looking at the mecha without sufficent eye protection. + visible_message(span("warning", "\The [energy_ball] explodes in a flash of light, sending a shock everywhere!")) + playsound(src.loc, 'sound/effects/lightningbolt.ogg', 100, 1, extrarange = 30) + tesla_zap(src.loc, 5, ELECTRIC_ZAP_POWER, FALSE) + for(var/mob/living/L in viewers(src)) + if(L == src) + continue + var/dir_towards_us = get_dir(L, src) + if(L.dir && L.dir & dir_towards_us) + to_chat(L, span("danger", "The flash of light blinds you briefly.")) + L.flash_eyes(intensity = FLASH_PROTECTION_MODERATE, override_blindness_check = FALSE, affect_silicon = TRUE) + + // Get rid of our energy ball. + energy_ball.stop_orbit() + qdel(energy_ball) + + sleep(1 SECOND) + // Resist resistance to old value. + shock_resist = old_shock_resist // Not using initial() in case the value gets modified by an admin or something. + +#undef ELECTRIC_ZAP_POWER + +/mob/living/simple_mob/mechanical/mecha/combat/gygax/dark/advanced/proc/launch_rockets(atom/target) + set waitfor = FALSE + + // Telegraph our next move. + Beam(target, icon_state = "sat_beam", time = 3.5 SECONDS, maxdistance = INFINITY) + visible_message(span("warning", "\The [src] deploys a missile rack!")) + playsound(src, 'sound/effects/turret/move1.wav', 50, 1) + sleep(0.5 SECONDS) + + for(var/i = 1 to 3) + if(target) // Might get deleted in the meantime. + var/turf/T = get_turf(target) + if(T) + visible_message(span("warning", "\The [src] fires a rocket into the air!")) + playsound(src, 'sound/weapons/rpg.ogg', 70, 1) + face_atom(T) + var/obj/item/projectile/arc/explosive_rocket/rocket = new(loc) + rocket.old_style_target(T, src) + rocket.fire() + sleep(1 SECOND) + + visible_message(span("warning", "\The [src] retracts the missile rack.")) + playsound(src, 'sound/effects/turret/move2.wav', 50, 1) + +// Arcing rocket projectile that produces a weak explosion when it lands. +// Shouldn't punch holes in the floor, but will still hurt. +/obj/item/projectile/arc/explosive_rocket + name = "rocket" + icon_state = "mortar" + +/obj/item/projectile/arc/explosive_rocket/on_impact(turf/T) + new /obj/effect/explosion(T) // Weak explosions don't produce this on their own, apparently. + explosion(T, 0, 0, 2, adminlog = FALSE) + +/mob/living/simple_mob/mechanical/mecha/combat/gygax/dark/advanced/proc/launch_microsingularity(atom/target) + var/turf/T = get_turf(target) + visible_message(span("warning", "\The [src] fires an energetic sphere into the air!")) + playsound(src, 'sound/weapons/Laser.ogg', 50, 1) + face_atom(T) + var/obj/item/projectile/arc/microsingulo/sphere = new(loc) + sphere.old_style_target(T, src) + sphere.fire() + +/obj/item/projectile/arc/microsingulo + name = "micro singularity" + icon_state = "bluespace" + +/obj/item/projectile/arc/microsingulo/on_impact(turf/T) + new /obj/effect/temporary_effect/pulse/microsingulo(T) + + +/obj/effect/temporary_effect/pulse/microsingulo + name = "micro singularity" + desc = "It's sucking everything in!" + icon = 'icons/obj/objects.dmi' + icon_state = "bhole3" + light_range = 4 + light_power = 5 + light_color = "#2ECCFA" + pulses_remaining = 10 + pulse_delay = 0.5 SECONDS + var/pull_radius = 3 + var/pull_strength = STAGE_THREE + +/obj/effect/temporary_effect/pulse/microsingulo/on_pulse() + for(var/atom/A in range(pull_radius, src)) + A.singularity_pull(src, pull_strength) + + +// The Advanced Dark Gygax's AI. +// The mob has three special attacks, based on the current intent. +// This AI choose the appropiate intent for the situation, and tries to ensure it doesn't kill itself by firing missiles at its feet. +/datum/ai_holder/simple_mob/intentional/adv_dark_gygax + conserve_ammo = TRUE // Might help avoid 'I shoot the wall forever' cheese. + var/closest_desired_distance = 1 // Otherwise run up to them to be able to potentially shock or punch them. + + var/electric_defense_radius = 3 // How big to assume electric defense's area is. + var/microsingulo_radius = 3 // Same but for microsingulo pull. + var/rocket_explosive_radius = 2 // Explosion radius for the rockets. + + var/electric_defense_threshold = 2 // How many non-targeted people are needed in close proximity before electric defense is viable. + var/microsingulo_threshold = 2 // Similar to above, but uses an area around the target. + +// Used to control the mob's positioning based on which special attack it has done. +// Note that the intent will not change again until the next special attack is about to happen. +/datum/ai_holder/simple_mob/intentional/adv_dark_gygax/on_engagement(atom/A) + // Make the AI backpeddle if using an AoE special attack. + var/list/risky_intents = list(I_GRAB, I_HURT) // Mini-singulo and missiles. + if(holder.a_intent in risky_intents) + var/closest_distance = 1 + switch(holder.a_intent) // Plus one just in case. + if(I_HURT) + closest_distance = rocket_explosive_radius + 1 + if(I_GRAB) + closest_distance = microsingulo_radius + 1 + + if(get_dist(holder, A) <= closest_distance) + holder.IMove(get_step_away(holder, A, closest_distance)) + + // Otherwise get up close and personal. + else if(get_dist(holder, A) > closest_desired_distance) + holder.IMove(get_step_towards(holder, A)) + +// Changes the mob's intent, which controls which special attack is used. +// I_DISARM causes Electric Defense, I_GRAB causes Micro-Singularity, and I_HURT causes Missile Barrage. +/datum/ai_holder/simple_mob/intentional/adv_dark_gygax/pre_special_attack(atom/A) + if(isliving(A)) + var/mob/living/target = A + + // If we're surrounded, Electric Defense will quickly fix that. + var/tally = 0 + var/list/potential_targets = list_targets() // Returns list of mobs and certain objects like mechs and turrets. + for(var/atom/movable/AM in potential_targets) + if(get_dist(holder, AM) > electric_defense_radius) + continue + if(!can_attack(AM)) + continue + tally++ + + // Should we shock them? + if(tally >= electric_defense_threshold || get_dist(target, holder) <= electric_defense_radius) + holder.a_intent = I_DISARM + return + + // Otherwise they're a fair distance away and we're not getting mobbed up close. + // See if we should use missiles or microsingulo. + tally = 0 // Let's recycle the var. + for(var/atom/movable/AM in potential_targets) + if(get_dist(target, AM) > microsingulo_radius) // Deliberately tests distance between target and nearby targets and not the holder. + continue + if(!can_attack(AM)) + continue + if(AM.anchored) // Microsingulo doesn't do anything to anchored things. + tally-- + else + tally++ + + // Lots of people means minisingulo would be more useful. + if(tally >= microsingulo_threshold) + holder.a_intent = I_GRAB + else // Otherwise use rockets. + holder.a_intent = I_HURT + + else + if(get_dist(holder, A) >= rocket_explosive_radius + 1) + holder.a_intent = I_HURT // Fire rockets if it's an obj/turf. + else + holder.a_intent = I_DISARM // Electricity might not work but it's safe up close. diff --git a/code/modules/mob/living/simple_mob/subtypes/slime/feral/feral.dm b/code/modules/mob/living/simple_mob/subtypes/slime/feral/feral.dm index 8986cbcbe3..91aa0b0a79 100644 --- a/code/modules/mob/living/simple_mob/subtypes/slime/feral/feral.dm +++ b/code/modules/mob/living/simple_mob/subtypes/slime/feral/feral.dm @@ -1,94 +1,94 @@ -// These slimes lack certain xenobio features but get more combat-oriented goodies. Generally these are more oriented towards Explorers than Xenobiologists. - -/mob/living/simple_mob/slime/feral - name = "feral slime" - desc = "The result of slimes escaping containment from some xenobiology lab. \ - Having the means to successfully escape their lab, as well as having to survive on a harsh, cold world has made these \ - creatures rival the ferocity of other apex predators in this region of Sif. It is considered to be a very invasive species." - description_info = "Note that processing this large slime will give six cores." - - cores = 6 // Xenobio will love getting their hands on these. - - icon_state = "slime adult" - icon_living = "slime adult" - icon_dead = "slime adult dead" - glow_range = 5 - glow_intensity = 4 - icon_scale = 2 // Twice as big as the xenobio variant. - pixel_y = -10 // Since the base sprite isn't centered properly, the pixel auto-adjustment needs some help. - default_pixel_y = -10 // To prevent resetting above var. - - maxHealth = 300 - movement_cooldown = 10 - melee_attack_delay = 0.5 SECONDS - - ai_holder_type = /datum/ai_holder/simple_mob/ranged/pointblank - - -// Slimebatoning/xenotasing it just makes it mad at you (which can be good if you're heavily armored and your friends aren't). -/mob/living/simple_mob/slime/feral/slimebatoned(mob/living/user, amount) - taunt(user, TRUE) - - -// *********** -// *Dark Blue* -// *********** - -// Dark Blue feral slimes can fire a strong icicle projectile every few seconds. The icicle hits hard and has some armor penetration. -// They also have a similar aura as their xenobio counterparts, which inflicts cold damage. It also chills non-resistant mobs. - -/mob/living/simple_mob/slime/feral/dark_blue - name = "dark blue feral slime" - color = "#2398FF" - glow_toggle = TRUE - slime_color = "dark blue" - coretype = /obj/item/slime_extract/dark_blue - cold_resist = 1 // Complete immunity. - minbodytemp = 0 - cold_damage_per_tick = 0 - - projectiletype = /obj/item/projectile/icicle - base_attack_cooldown = 2 SECONDS - ranged_attack_delay = 1 SECOND - - player_msg = "You can fire an icicle projectile every two seconds. It hits hard, and armor has a hard time resisting it.
\ - You are also immune to the cold, and you cause enemies around you to suffer periodic harm from the cold, if unprotected.
\ - Unprotected enemies are also Chilled, making them slower and less evasive, and disabling effects last longer." - -/obj/item/projectile/icicle - name = "icicle" - icon_state = "ice_2" - damage = 40 - damage_type = BRUTE - check_armour = "melee" - armor_penetration = 30 - step_delay = 2 // Make it a bit easier to dodge since its not a bullet. - icon_scale = 2 // It hits like a truck. - sharp = TRUE - -/obj/item/projectile/icicle/on_impact(atom/A) - playsound(get_turf(A), "shatter", 70, 1) - return ..() - -/obj/item/projectile/icicle/get_structure_damage() - return damage / 2 // They're really deadly against mobs, but less effective against solid things. - -/mob/living/simple_mob/slime/feral/dark_blue/handle_special() - if(stat != DEAD) - cold_aura() - ..() - -/mob/living/simple_mob/slime/feral/dark_blue/proc/cold_aura() - for(var/mob/living/L in view(3, src)) - if(L == src) - continue - chill(L) - -/mob/living/simple_mob/slime/feral/dark_blue/proc/chill(mob/living/L) - L.inflict_cold_damage(10) - if(L.get_cold_protection() < 1) - L.add_modifier(/datum/modifier/chilled, 5 SECONDS, src) - - if(L.has_AI()) // Other AIs should react to hostile auras. - L.ai_holder.react_to_attack(src) - +// These slimes lack certain xenobio features but get more combat-oriented goodies. Generally these are more oriented towards Explorers than Xenobiologists. + +/mob/living/simple_mob/slime/feral + name = "feral slime" + desc = "The result of slimes escaping containment from some xenobiology lab. \ + Having the means to successfully escape their lab, as well as having to survive on a harsh, cold world has made these \ + creatures rival the ferocity of other apex predators in this region of Sif. It is considered to be a very invasive species." + description_info = "Note that processing this large slime will give six cores." + + cores = 6 // Xenobio will love getting their hands on these. + + icon_state = "slime adult" + icon_living = "slime adult" + icon_dead = "slime adult dead" + glow_range = 5 + glow_intensity = 4 + icon_scale = 2 // Twice as big as the xenobio variant. + pixel_y = -10 // Since the base sprite isn't centered properly, the pixel auto-adjustment needs some help. + default_pixel_y = -10 // To prevent resetting above var. + + maxHealth = 300 + movement_cooldown = 10 + melee_attack_delay = 0.5 SECONDS + + ai_holder_type = /datum/ai_holder/simple_mob/ranged/pointblank + + +// Slimebatoning/xenotasing it just makes it mad at you (which can be good if you're heavily armored and your friends aren't). +/mob/living/simple_mob/slime/feral/slimebatoned(mob/living/user, amount) + taunt(user, TRUE) + + +// *********** +// *Dark Blue* +// *********** + +// Dark Blue feral slimes can fire a strong icicle projectile every few seconds. The icicle hits hard and has some armor penetration. +// They also have a similar aura as their xenobio counterparts, which inflicts cold damage. It also chills non-resistant mobs. + +/mob/living/simple_mob/slime/feral/dark_blue + name = "dark blue feral slime" + color = "#2398FF" + glow_toggle = TRUE + slime_color = "dark blue" + coretype = /obj/item/slime_extract/dark_blue + cold_resist = 1 // Complete immunity. + minbodytemp = 0 + cold_damage_per_tick = 0 + + projectiletype = /obj/item/projectile/icicle + base_attack_cooldown = 2 SECONDS + ranged_attack_delay = 1 SECOND + + player_msg = "You can fire an icicle projectile every two seconds. It hits hard, and armor has a hard time resisting it.
\ + You are also immune to the cold, and you cause enemies around you to suffer periodic harm from the cold, if unprotected.
\ + Unprotected enemies are also Chilled, making them slower and less evasive, and disabling effects last longer." + +/obj/item/projectile/icicle + name = "icicle" + icon_state = "ice_2" + damage = 40 + damage_type = BRUTE + check_armour = "melee" + armor_penetration = 30 + speed = 2 + icon_scale = 2 // It hits like a truck. + sharp = TRUE + +/obj/item/projectile/icicle/on_impact(atom/A) + playsound(get_turf(A), "shatter", 70, 1) + return ..() + +/obj/item/projectile/icicle/get_structure_damage() + return damage / 2 // They're really deadly against mobs, but less effective against solid things. + +/mob/living/simple_mob/slime/feral/dark_blue/handle_special() + if(stat != DEAD) + cold_aura() + ..() + +/mob/living/simple_mob/slime/feral/dark_blue/proc/cold_aura() + for(var/mob/living/L in view(3, src)) + if(L == src) + continue + chill(L) + +/mob/living/simple_mob/slime/feral/dark_blue/proc/chill(mob/living/L) + L.inflict_cold_damage(10) + if(L.get_cold_protection() < 1) + L.add_modifier(/datum/modifier/chilled, 5 SECONDS, src) + + if(L.has_AI()) // Other AIs should react to hostile auras. + L.ai_holder.react_to_attack(src) + diff --git a/code/modules/mob/mob.dm b/code/modules/mob/mob.dm index 18ee81ac2b..6454cb6b8f 100644 --- a/code/modules/mob/mob.dm +++ b/code/modules/mob/mob.dm @@ -672,10 +672,12 @@ . = (is_client_active(10 MINUTES)) if(.) - if(statpanel("Status") && ticker && ticker.current_state != GAME_STATE_PREGAME) - stat("Station Time", stationtime2text()) - stat("Station Date", stationdate2text()) - stat("Round Duration", roundduration2text()) + if(statpanel("Status")) + stat(null, "Time Dilation: [round(SStime_track.time_dilation_current,1)]% AVG:([round(SStime_track.time_dilation_avg_fast,1)]%, [round(SStime_track.time_dilation_avg,1)]%, [round(SStime_track.time_dilation_avg_slow,1)]%)") + if(ticker && ticker.current_state != GAME_STATE_PREGAME) + stat("Station Time", stationtime2text()) + stat("Station Date", stationdate2text()) + stat("Round Duration", roundduration2text()) if(client.holder) if(statpanel("Status")) @@ -1192,6 +1194,8 @@ mob/proc/yank_out_object() closeToolTip(usr) //No reason not to, really ..() +<<<<<<< HEAD +======= // Manages a global list of mobs with clients attached, indexed by z-level. /mob/proc/update_client_z(new_z) // +1 to register, null to unregister. @@ -1205,6 +1209,7 @@ mob/proc/yank_out_object() else registered_z = null -/mob/on_z_change(old_z, new_z) +/mob/onTransitZ(old_z, new_z) ..() - update_client_z(new_z) \ No newline at end of file + update_client_z(new_z) +>>>>>>> 9ff8103... Merge pull request #5636 from kevinz000/pixel_projectiles diff --git a/code/modules/mob/mob_movement.dm b/code/modules/mob/mob_movement.dm index 58b549cbc4..57ba80849b 100644 --- a/code/modules/mob/mob_movement.dm +++ b/code/modules/mob/mob_movement.dm @@ -8,7 +8,6 @@ return (!mover.density || !density || lying) else return (!mover.density || !density || lying) - return /mob/proc/setMoveCooldown(var/timeout) move_delay = max(world.time + timeout, move_delay) @@ -18,22 +17,6 @@ return FALSE // Need to wait more. return TRUE -/client/North() - ..() - - -/client/South() - ..() - - -/client/West() - ..() - - -/client/East() - ..() - - /client/proc/client_dir(input, direction=-1) return turn(input, direction*dir2angle(dir)) @@ -126,6 +109,7 @@ */ return +<<<<<<< HEAD //This proc should never be overridden elsewhere at /atom/movable to keep directions sane. /atom/movable/Move(newloc, direct) if (direct & (direct - 1)) @@ -182,6 +166,8 @@ /atom/movable/proc/Moved(atom/oldloc) return +======= +>>>>>>> 9ff8103... Merge pull request #5636 from kevinz000/pixel_projectiles /client/proc/Move_object(direct) if(mob && mob.control_object) if(mob.control_object.density) diff --git a/code/modules/mob/new_player/new_player.dm b/code/modules/mob/new_player/new_player.dm index ea27828385..4b2f7350ed 100644 --- a/code/modules/mob/new_player/new_player.dm +++ b/code/modules/mob/new_player/new_player.dm @@ -121,7 +121,7 @@ var/mob/living/carbon/human/dummy/mannequin = new() client.prefs.dress_preview_mob(mannequin) var/mob/observer/dead/observer = new(mannequin) - observer.forceMove(null) //Let's not stay in our doomed mannequin + observer.moveToNullspace() //Let's not stay in our doomed mannequin qdel(mannequin) spawning = 1 diff --git a/code/modules/power/singularity/act.dm b/code/modules/power/singularity/act.dm index a5d7dc3651..5d33ee440e 100644 --- a/code/modules/power/singularity/act.dm +++ b/code/modules/power/singularity/act.dm @@ -75,7 +75,6 @@ return /obj/machinery/power/supermatter/shard/singularity_act() - src.forceMove(null) qdel(src) return 5000 @@ -90,7 +89,6 @@ SetUniversalState(/datum/universal_state/supermatter_cascade) log_admin("New super singularity made by eating a SM crystal [prints]. Last touched by [src.fingerprintslast].") message_admins("New super singularity made by eating a SM crystal [prints]. Last touched by [src.fingerprintslast].") - src.forceMove(null) qdel(src) return 50000 diff --git a/code/modules/power/singularity/emitter.dm b/code/modules/power/singularity/emitter.dm index ac8be2b77f..f847813923 100644 --- a/code/modules/power/singularity/emitter.dm +++ b/code/modules/power/singularity/emitter.dm @@ -142,7 +142,8 @@ var/obj/item/projectile/beam/emitter/A = get_emitter_beam() A.damage = round(power_per_shot/EMITTER_DAMAGE_POWER_TRANSFER) - A.launch( get_step(src.loc, src.dir) ) + A.firer = src + A.fire(dir2angle(dir)) /obj/machinery/power/emitter/attackby(obj/item/W, mob/user) diff --git a/code/modules/projectiles/gun.dm b/code/modules/projectiles/gun.dm index e6d15837d0..13b03d5992 100644 --- a/code/modules/projectiles/gun.dm +++ b/code/modules/projectiles/gun.dm @@ -470,7 +470,8 @@ P.shot_from = src.name P.silenced = silenced - P.launch(target) + P.old_style_target(target) + P.fire() last_shot = world.time @@ -645,24 +646,17 @@ /obj/item/weapon/gun/proc/process_projectile(obj/projectile, mob/user, atom/target, var/target_zone, var/params=null) var/obj/item/projectile/P = projectile if(!istype(P)) - return 0 //default behaviour only applies to true projectiles - - if(params) - P.set_clickpoint(params) + return FALSE //default behaviour only applies to true projectiles //shooting while in shock - var/x_offset = 0 - var/y_offset = 0 + var/forcespread if(istype(user, /mob/living/carbon)) var/mob/living/carbon/mob = user if(mob.shock_stage > 120) - y_offset = rand(-2,2) - x_offset = rand(-2,2) + forcespread = rand(50, 50) else if(mob.shock_stage > 70) - y_offset = rand(-1,1) - x_offset = rand(-1,1) - - var/launched = !P.launch_from_gun(target, user, src, target_zone, x_offset, y_offset) + forcespread = rand(-25, 25) + var/launched = !P.launch_from_gun(target, target_zone, user, params, null, forcespread, src) if(launched) play_fire_sound(user, P) diff --git a/code/modules/projectiles/guns/projectile/dartgun.dm b/code/modules/projectiles/guns/projectile/dartgun.dm index 080c7cc1a6..4958accb64 100644 --- a/code/modules/projectiles/guns/projectile/dartgun.dm +++ b/code/modules/projectiles/guns/projectile/dartgun.dm @@ -3,7 +3,7 @@ icon_state = "dart" damage = 5 var/reagent_amount = 15 - kill_count = 15 //shorter range + range = 15 //shorter range muzzle_type = null diff --git a/code/modules/projectiles/guns/vox.dm b/code/modules/projectiles/guns/vox.dm index 83b3f1eb9e..0a98a1cc61 100644 --- a/code/modules/projectiles/guns/vox.dm +++ b/code/modules/projectiles/guns/vox.dm @@ -79,9 +79,9 @@ damage_type = HALLOSS light_color = "#8837A3" - muzzle_type = /obj/effect/projectile/darkmatterstun/muzzle - tracer_type = /obj/effect/projectile/darkmatterstun/tracer - impact_type = /obj/effect/projectile/darkmatterstun/impact + muzzle_type = /obj/effect/projectile/muzzle/darkmatterstun + tracer_type = /obj/effect/projectile/tracer/darkmatterstun + impact_type = /obj/effect/projectile/impact/darkmatterstun /obj/item/projectile/beam/darkmatter name = "dark matter bolt" @@ -95,9 +95,9 @@ embed_chance = 0 - muzzle_type = /obj/effect/projectile/darkmatter/muzzle - tracer_type = /obj/effect/projectile/darkmatter/tracer - impact_type = /obj/effect/projectile/darkmatter/impact + muzzle_type = /obj/effect/projectile/muzzle/darkmatter + tracer_type = /obj/effect/projectile/tracer/darkmatter + impact_type = /obj/effect/projectile/impact/darkmatter /obj/item/projectile/energy/darkmatter name = "dark matter pellet" diff --git a/code/modules/projectiles/projectile.dm b/code/modules/projectiles/projectile.dm index 7e92da9e46..e1a80ed1ab 100644 --- a/code/modules/projectiles/projectile.dm +++ b/code/modules/projectiles/projectile.dm @@ -1,37 +1,84 @@ -/* -#define BRUTE "brute" -#define BURN "burn" -#define TOX "tox" -#define OXY "oxy" -#define CLONE "clone" - -#define ADD "add" -#define SET "set" -*/ +#define MOVES_HITSCAN -1 //Not actually hitscan but close as we get without actual hitscan. +#define MUZZLE_EFFECT_PIXEL_INCREMENT 17 //How many pixels to move the muzzle flash up so your character doesn't look like they're shitting out lasers. /obj/item/projectile name = "projectile" icon = 'icons/obj/projectiles.dmi' icon_state = "bullet" - density = 1 - unacidable = 1 - anchored = 1 //There's a reason this is here, Mport. God fucking damn it -Agouri. Find&Fix by Pete. The reason this is here is to stop the curving of emitter shots. + density = FALSE + anchored = TRUE + unacidable = TRUE pass_flags = PASSTABLE mouse_opacity = 0 - var/bumped = 0 //Prevents it from hitting more than one guy at once + + ////TG PROJECTILE SYTSEM + //Projectile stuff + var/range = 50 + var/originalRange + + //Fired processing vars + var/fired = FALSE //Have we been fired yet + var/paused = FALSE //for suspending the projectile midair + var/last_projectile_move = 0 + var/last_process = 0 + var/time_offset = 0 + var/datum/point/vector/trajectory + var/trajectory_ignore_forcemove = FALSE //instructs forceMove to NOT reset our trajectory to the new location! + + var/speed = 0.8 //Amount of deciseconds it takes for projectile to travel + var/Angle = 0 + var/original_angle = 0 //Angle at firing + var/nondirectional_sprite = FALSE //Set TRUE to prevent projectiles from having their sprites rotated based on firing angle + var/spread = 0 //amount (in degrees) of projectile spread + animate_movement = 0 //Use SLIDE_STEPS in conjunction with legacy + var/ricochets = 0 + var/ricochets_max = 2 + var/ricochet_chance = 30 + + //Hitscan + var/hitscan = FALSE //Whether this is hitscan. If it is, speed is basically ignored. + var/list/beam_segments //assoc list of datum/point or datum/point/vector, start = end. Used for hitscan effect generation. + var/datum/point/beam_index + var/turf/hitscan_last //last turf touched during hitscanning. + var/tracer_type + var/muzzle_type + var/impact_type + + //Fancy hitscan lighting effects! + var/hitscan_light_intensity = 1.5 + var/hitscan_light_range = 0.75 + var/hitscan_light_color_override + var/muzzle_flash_intensity = 3 + var/muzzle_flash_range = 1.5 + var/muzzle_flash_color_override + var/impact_light_intensity = 3 + var/impact_light_range = 2 + var/impact_light_color_override + + //Homing + var/homing = FALSE + var/atom/homing_target + var/homing_turn_speed = 10 //Angle per tick. + var/homing_inaccuracy_min = 0 //in pixels for these. offsets are set once when setting target. + var/homing_inaccuracy_max = 0 + var/homing_offset_x = 0 + var/homing_offset_y = 0 + + //Targetting + var/yo = null + var/xo = null + var/atom/original = null // the original target clicked + var/turf/starting = null // the projectile's starting turf + var/list/permutated = list() // we've passed through these atoms, don't try to hit them again + var/p_x = 16 + var/p_y = 16 // the pixel location of the tile that the player clicked. Default is the center + + //Misc/Polaris variables + var/def_zone = "" //Aiming at var/mob/firer = null//Who shot it var/silenced = 0 //Attack message - var/yo = null - var/xo = null - var/current = null var/shot_from = "" // name of the object which shot us - var/atom/original = null // the target clicked (not necessarily where the projectile is headed). Should probably be renamed to 'target' or something. - var/turf/starting = null // the projectile's starting turf - var/list/permutated = list() // we've passed through these atoms, don't try to hit them again - - var/p_x = 16 - var/p_y = 16 // the pixel location of the tile that the player clicked. Default is the center var/accuracy = 0 var/dispersion = 0.0 @@ -45,7 +92,6 @@ var/check_armour = "bullet" //Defines what armor to use when it hits things. Must be set to bullet, laser, energy,or bomb //Cael - bio and rad are also valid var/projectile_type = /obj/item/projectile var/penetrating = 0 //If greater than zero, the projectile will pass through dense objects as specified by on_penetrate() - var/kill_count = 50 //This will de-increment every process(). When 0, it will delete the projectile. //Effects var/incendiary = 0 //1 for ignite on hit, 2 for trail of fire. 3 maybe later for burst of fire around the impact point. - Mech var/flammability = 0 //Amount of fire stacks to add for the above. @@ -64,23 +110,127 @@ embed_chance = 0 //Base chance for a projectile to embed - var/hitscan = 0 // whether the projectile should be hitscan - var/step_delay = 1 // the delay between iterations if not a hitscan projectile - - // effect types to be used - var/muzzle_type - var/tracer_type - var/impact_type - var/fire_sound = 'sound/weapons/Gunshot_old.ogg' // Can be overriden in gun.dm's fire_sound var. It can also be null but I don't know why you'd ever want to do that. -Ace - var/vacuum_traversal = 1 //Determines if the projectile can exist in vacuum, if false, the projectile will be deleted if it enters vacuum. + var/vacuum_traversal = TRUE //Determines if the projectile can exist in vacuum, if false, the projectile will be deleted if it enters vacuum. - var/datum/plot_vector/trajectory // used to plot the path of the projectile - var/datum/vector_loc/location // current location of the projectile in pixel space - var/matrix/effect_transform // matrix to rotate and scale projectile effects - putting it here so it doesn't - // have to be recreated multiple times +/obj/item/projectile/proc/Range() + range-- + if(range <= 0 && loc) + on_range() +/obj/item/projectile/proc/on_range() //if we want there to be effects when they reach the end of their range + qdel(src) + +/obj/item/projectile/proc/return_predicted_turf_after_moves(moves, forced_angle) //I say predicted because there's no telling that the projectile won't change direction/location in flight. + if(!trajectory && isnull(forced_angle) && isnull(Angle)) + return FALSE + var/datum/point/vector/current = trajectory + if(!current) + var/turf/T = get_turf(src) + current = new(T.x, T.y, T.z, pixel_x, pixel_y, isnull(forced_angle)? Angle : forced_angle, SSprojectiles.global_pixel_speed) + var/datum/point/vector/v = current.return_vector_after_increments(moves * SSprojectiles.global_iterations_per_move) + return v.return_turf() + +/obj/item/projectile/proc/return_pathing_turfs_in_moves(moves, forced_angle) + var/turf/current = get_turf(src) + var/turf/ending = return_predicted_turf_after_moves(moves, forced_angle) + return getline(current, ending) + +/obj/item/projectile/proc/set_pixel_speed(new_speed) + if(trajectory) + trajectory.set_speed(new_speed) + return TRUE + return FALSE + +/obj/item/projectile/proc/record_hitscan_start(datum/point/pcache) + if(pcache) + beam_segments = list() + beam_index = pcache + beam_segments[beam_index] = null //record start. + +/obj/item/projectile/proc/process_hitscan() + var/safety = range * 3 + record_hitscan_start(RETURN_POINT_VECTOR_INCREMENT(src, Angle, MUZZLE_EFFECT_PIXEL_INCREMENT, 1)) + while(loc && !QDELETED(src)) + if(paused) + stoplag(1) + continue + if(safety-- <= 0) + if(loc) + Bump(loc) + if(!QDELETED(src)) + qdel(src) + return //Kill! + pixel_move(1, TRUE) + +/obj/item/projectile/proc/pixel_move(trajectory_multiplier, hitscanning = FALSE) + if(!loc || !trajectory) + return + last_projectile_move = world.time + if(homing) + process_homing() + var/forcemoved = FALSE + for(var/i in 1 to SSprojectiles.global_iterations_per_move) + if(QDELETED(src)) + return + trajectory.increment(trajectory_multiplier) + var/turf/T = trajectory.return_turf() + if(!istype(T)) + qdel(src) + return + if(T.z != loc.z) + var/old = loc + before_z_change(loc, T) + trajectory_ignore_forcemove = TRUE + forceMove(T) + trajectory_ignore_forcemove = FALSE + after_z_change(old, loc) + if(!hitscanning) + pixel_x = trajectory.return_px() + pixel_y = trajectory.return_py() + forcemoved = TRUE + hitscan_last = loc + else if(T != loc) + before_move() + step_towards(src, T) + hitscan_last = loc + after_move() + if(can_hit_target(original, permutated)) + Bump(original) + if(!hitscanning && !forcemoved) + pixel_x = trajectory.return_px() - trajectory.mpx * trajectory_multiplier * SSprojectiles.global_iterations_per_move + pixel_y = trajectory.return_py() - trajectory.mpy * trajectory_multiplier * SSprojectiles.global_iterations_per_move + animate(src, pixel_x = trajectory.return_px(), pixel_y = trajectory.return_py(), time = 1, flags = ANIMATION_END_NOW) + Range() + +/obj/item/projectile/Crossed(atom/movable/AM) //A mob moving on a tile with a projectile is hit by it. + ..() + if(isliving(AM) && (AM.density || AM == original)) + Bump(AM) + +/obj/item/projectile/proc/process_homing() //may need speeding up in the future performance wise. + if(!homing_target) + return FALSE + var/datum/point/PT = RETURN_PRECISE_POINT(homing_target) + PT.x += CLAMP(homing_offset_x, 1, world.maxx) + PT.y += CLAMP(homing_offset_y, 1, world.maxy) + var/angle = closer_angle_difference(Angle, angle_between_points(RETURN_PRECISE_POINT(src), PT)) + setAngle(Angle + CLAMP(angle, -homing_turn_speed, homing_turn_speed)) + +/obj/item/projectile/proc/set_homing_target(atom/A) + if(!A || (!isturf(A) && !isturf(A.loc))) + return FALSE + homing = TRUE + homing_target = A + homing_offset_x = rand(homing_inaccuracy_min, homing_inaccuracy_max) + homing_offset_y = rand(homing_inaccuracy_min, homing_inaccuracy_max) + if(prob(50)) + homing_offset_x = -homing_offset_x + if(prob(50)) + homing_offset_y = -homing_offset_y + +<<<<<<< HEAD //TODO: make it so this is called more reliably, instead of sometimes by bullet_act() and sometimes not /obj/item/projectile/proc/on_hit(var/atom/target, var/blocked = 0, var/def_zone = null) if(blocked >= 100) return 0//Full block @@ -91,151 +241,247 @@ if(modifier_type_to_apply) L.add_modifier(modifier_type_to_apply, modifier_duration) return 1 +======= +/obj/item/projectile/process() + last_process = world.time + if(!loc || !fired || !trajectory) + fired = FALSE + return PROCESS_KILL + if(paused || !isturf(loc)) + last_projectile_move += world.time - last_process //Compensates for pausing, so it doesn't become a hitscan projectile when unpaused from charged up ticks. + return + var/elapsed_time_deciseconds = (world.time - last_projectile_move) + time_offset + time_offset = 0 + var/required_moves = speed > 0? FLOOR(elapsed_time_deciseconds / speed, 1) : MOVES_HITSCAN //Would be better if a 0 speed made hitscan but everyone hates those so I can't make it a universal system :< + if(required_moves == MOVES_HITSCAN) + required_moves = SSprojectiles.global_max_tick_moves + else + if(required_moves > SSprojectiles.global_max_tick_moves) + var/overrun = required_moves - SSprojectiles.global_max_tick_moves + required_moves = SSprojectiles.global_max_tick_moves + time_offset += overrun * speed + time_offset += MODULUS(elapsed_time_deciseconds, speed) -//called when the projectile stops flying because it collided with something -/obj/item/projectile/proc/on_impact(var/atom/A) - impact_effect(effect_transform) // generate impact effect - if(damage && damage_type == BURN) - var/turf/T = get_turf(A) - if(T) - T.hotspot_expose(700, 5) + for(var/i in 1 to required_moves) + pixel_move(1, FALSE) + +/obj/item/projectile/proc/setAngle(new_angle) //wrapper for overrides. + Angle = new_angle + if(!nondirectional_sprite) + var/matrix/M = new + M.Turn(Angle) + transform = M + if(trajectory) + trajectory.set_angle(new_angle) + return TRUE + +/obj/item/projectile/forceMove(atom/target) + if(!isloc(target) || !isloc(loc) || !z) + return ..() + var/zc = target.z != z + var/old = loc + if(zc) + before_z_change(old, target) + . = ..() + if(trajectory && !trajectory_ignore_forcemove && isturf(target)) + if(hitscan) + finalize_hitscan_and_generate_tracers(FALSE) + trajectory.initialize_location(target.x, target.y, target.z, 0, 0) + if(hitscan) + record_hitscan_start(RETURN_PRECISE_POINT(src)) + if(zc) + after_z_change(old, target) + +/obj/item/projectile/proc/fire(angle, atom/direct_target) + //If no angle needs to resolve it from xo/yo! + if(direct_target) + direct_target.bullet_act(src, def_zone) + qdel(src) + return + if(isnum(angle)) + setAngle(angle) + var/turf/starting = get_turf(src) + if(isnull(Angle)) //Try to resolve through offsets if there's no angle set. + if(isnull(xo) || isnull(yo)) + crash_with("WARNING: Projectile [type] deleted due to being unable to resolve a target after angle was null!") + qdel(src) + return + var/turf/target = locate(CLAMP(starting + xo, 1, world.maxx), CLAMP(starting + yo, 1, world.maxy), starting.z) + setAngle(Get_Angle(src, target)) + if(dispersion) + setAngle(Angle + rand(-dispersion, dispersion)) + original_angle = Angle + trajectory_ignore_forcemove = TRUE + forceMove(starting) + trajectory_ignore_forcemove = FALSE + trajectory = new(starting.x, starting.y, starting.z, pixel_x, pixel_y, Angle, SSprojectiles.global_pixel_speed) + last_projectile_move = world.time + permutated = list() + originalRange = range + fired = TRUE + if(hitscan) + process_hitscan() + START_PROCESSING(SSprojectiles, src) + pixel_move(1, FALSE) //move it now! + +/obj/item/projectile/proc/after_z_change(atom/olcloc, atom/newloc) + +/obj/item/projectile/proc/before_z_change(atom/oldloc, atom/newloc) +>>>>>>> 9ff8103... Merge pull request #5636 from kevinz000/pixel_projectiles + +/obj/item/projectile/proc/before_move() return -//Checks if the projectile is eligible for embedding. Not that it necessarily will. -/obj/item/projectile/proc/can_embed() - //embed must be enabled and damage type must be brute - if(embed_chance == 0 || damage_type != BRUTE) - return 0 - return 1 +/obj/item/projectile/proc/after_move() + return -/obj/item/projectile/proc/get_structure_damage() - if(damage_type == BRUTE || damage_type == BURN) - return damage - return 0 +/obj/item/projectile/proc/store_hitscan_collision(datum/point/pcache) + beam_segments[beam_index] = pcache + beam_index = pcache + beam_segments[beam_index] = null -//return 1 if the projectile should be allowed to pass through after all, 0 if not. -/obj/item/projectile/proc/check_penetrate(var/atom/A) - return 1 +//Spread is FORCED! +/obj/item/projectile/proc/preparePixelProjectile(atom/target, atom/source, params, spread = 0) + var/turf/curloc = get_turf(source) + var/turf/targloc = get_turf(target) + trajectory_ignore_forcemove = TRUE + forceMove(get_turf(source)) + trajectory_ignore_forcemove = FALSE + starting = get_turf(source) + original = target + if(targloc || !params) + yo = targloc.y - curloc.y + xo = targloc.x - curloc.x + setAngle(Get_Angle(src, targloc) + spread) -/obj/item/projectile/proc/check_fire(atom/target as mob, var/mob/living/user as mob) //Checks if you can hit them or not. - check_trajectory(target, user, pass_flags, flags) + if(isliving(source) && params) + var/list/calculated = calculate_projectile_angle_and_pixel_offsets(source, params) + p_x = calculated[2] + p_y = calculated[3] -//sets the click point of the projectile using mouse input params -/obj/item/projectile/proc/set_clickpoint(var/params) + setAngle(calculated[1] + spread) + else if(targloc) + yo = targloc.y - curloc.y + xo = targloc.x - curloc.x + setAngle(Get_Angle(src, targloc) + spread) + else + crash_with("WARNING: Projectile [type] fired without either mouse parameters, or a target atom to aim at!") + qdel(src) + +/proc/calculate_projectile_angle_and_pixel_offsets(mob/user, params) var/list/mouse_control = params2list(params) + var/p_x = 0 + var/p_y = 0 + var/angle = 0 if(mouse_control["icon-x"]) p_x = text2num(mouse_control["icon-x"]) if(mouse_control["icon-y"]) p_y = text2num(mouse_control["icon-y"]) + if(mouse_control["screen-loc"]) + //Split screen-loc up into X+Pixel_X and Y+Pixel_Y + var/list/screen_loc_params = splittext(mouse_control["screen-loc"], ",") - //randomize clickpoint a bit based on dispersion - if(dispersion) - var/radius = round((dispersion*0.443)*world.icon_size*0.8) //0.443 = sqrt(pi)/4 = 2a, where a is the side length of a square that shares the same area as a circle with diameter = dispersion - p_x = between(0, p_x + rand(-radius, radius), world.icon_size) - p_y = between(0, p_y + rand(-radius, radius), world.icon_size) + //Split X+Pixel_X up into list(X, Pixel_X) + var/list/screen_loc_X = splittext(screen_loc_params[1],":") -//called to launch a projectile -/obj/item/projectile/proc/launch(atom/target, var/target_zone, var/x_offset=0, var/y_offset=0, var/angle_offset=0) - var/turf/curloc = get_turf(src) - var/turf/targloc = get_turf(target) - if (!istype(targloc) || !istype(curloc)) - return 1 + //Split Y+Pixel_Y up into list(Y, Pixel_Y) + var/list/screen_loc_Y = splittext(screen_loc_params[2],":") + var/x = text2num(screen_loc_X[1]) * 32 + text2num(screen_loc_X[2]) - 32 + var/y = text2num(screen_loc_Y[1]) * 32 + text2num(screen_loc_Y[2]) - 32 - if(combustion) - curloc.hotspot_expose(700, 5) + //Calculate the "resolution" of screen based on client's view and world's icon size. This will work if the user can view more tiles than average. + var/list/screenview = user.client? getviewsize(user.client.view) : world.view + var/screenviewX = screenview[1] * world.icon_size + var/screenviewY = screenview[2] * world.icon_size - if(targloc == curloc) //Shooting something in the same turf - target.bullet_act(src, target_zone) - on_impact(target) - qdel(src) - return 0 + var/ox = round(screenviewX/2) - user.client.pixel_x //"origin" x + var/oy = round(screenviewY/2) - user.client.pixel_y //"origin" y + angle = ATAN2(y - oy, x - ox) + return list(angle, p_x, p_y) +/obj/item/projectile/proc/redirect(x, y, starting, source) + old_style_target(locate(x, y, z), starting? get_turf(starting) : get_turf(source)) + +/obj/item/projectile/proc/old_style_target(atom/target, atom/source) + if(!source) + source = get_turf(src) + starting = source original = target - def_zone = target_zone + setAngle(Get_Angle(source, target)) - spawn() - setup_trajectory(curloc, targloc, x_offset, y_offset, angle_offset) //plot the initial trajectory - process() +/obj/item/projectile/Destroy() + if(hitscan) + finalize_hitscan_and_generate_tracers() + STOP_PROCESSING(SSprojectiles, src) + cleanup_beam_segments() + qdel(trajectory) + return ..() - return 0 +/obj/item/projectile/proc/cleanup_beam_segments() + QDEL_LIST_ASSOC(beam_segments) + beam_segments = list() + qdel(beam_index) -//called to launch a projectile from a gun -/obj/item/projectile/proc/launch_from_gun(atom/target, mob/user, obj/item/weapon/gun/launcher, var/target_zone, var/x_offset=0, var/y_offset=0) - if(user == target) //Shooting yourself - user.bullet_act(src, target_zone) - on_impact(user) - qdel(src) - return 0 - - loc = get_turf(user) //move the projectile out into the world - - firer = user - shot_from = launcher.name - silenced = launcher.silenced - - return launch(target, target_zone, x_offset, y_offset) - -//Used to change the direction of the projectile in flight. -/obj/item/projectile/proc/redirect(var/new_x, var/new_y, var/atom/starting_loc, var/mob/new_firer=null) - var/turf/new_target = locate(new_x, new_y, src.z) - - original = new_target - if(new_firer) - firer = src - - setup_trajectory(starting_loc, new_target) - -//Called when the projectile intercepts a mob. Returns 1 if the projectile hit the mob, 0 if it missed and should keep flying. -/obj/item/projectile/proc/attack_mob(var/mob/living/target_mob, var/distance, var/miss_modifier=0) - if(!istype(target_mob)) - return - - //roll to-hit - miss_modifier = max(15*(distance-2) - accuracy + miss_modifier + target_mob.get_evasion(), 0) - var/hit_zone = get_zone_with_miss_chance(def_zone, target_mob, miss_modifier, ranged_attack=(distance > 1 || original != target_mob)) //if the projectile hits a target we weren't originally aiming at then retain the chance to miss - - var/result = PROJECTILE_FORCE_MISS - if(hit_zone) - def_zone = hit_zone //set def_zone, so if the projectile ends up hitting someone else later (to be implemented), it is more likely to hit the same part - result = target_mob.bullet_act(src, def_zone) - - if(result == PROJECTILE_FORCE_MISS) - if(!silenced) - visible_message("\The [src] misses [target_mob] narrowly!") - return 0 - - //hit messages - if(silenced) - to_chat(target_mob, "You've been hit in the [parse_zone(def_zone)] by \the [src]!") +/obj/item/projectile/proc/vol_by_damage() + if(damage) + return CLAMP((damage) * 0.67, 30, 100)// Multiply projectile damage by 0.67, then CLAMP the value between 30 and 100 else - visible_message("\The [target_mob] is hit by \the [src] in the [parse_zone(def_zone)]!")//X has fired Y is now given by the guns so you cant tell who shot you if you could not see the shooter + return 50 //if the projectile doesn't do damage, play its hitsound at 50% volume. - //admin logs - if(!no_attack_log) - if(istype(firer, /mob) && istype(target_mob)) - add_attack_logs(firer,target_mob,"Shot with \a [src.type] projectile") +/obj/item/projectile/proc/finalize_hitscan_and_generate_tracers(impacting = TRUE) + if(trajectory && beam_index) + var/datum/point/pcache = trajectory.copy_to() + beam_segments[beam_index] = pcache + generate_hitscan_tracers(null, null, impacting) - //sometimes bullet_act() will want the projectile to continue flying - if (result == PROJECTILE_CONTINUE) - return 0 +/obj/item/projectile/proc/generate_hitscan_tracers(cleanup = TRUE, duration = 3, impacting = TRUE) + if(!length(beam_segments)) + return + if(tracer_type) + var/tempref = "\ref[src]" + for(var/datum/point/p in beam_segments) + generate_tracer_between_points(p, beam_segments[p], tracer_type, color, duration, hitscan_light_range, hitscan_light_color_override, hitscan_light_intensity, tempref) + if(muzzle_type && duration > 0) + var/datum/point/p = beam_segments[1] + var/atom/movable/thing = new muzzle_type + p.move_atom_to_src(thing) + var/matrix/M = new + M.Turn(original_angle) + thing.transform = M + thing.color = color + thing.set_light(muzzle_flash_range, muzzle_flash_intensity, muzzle_flash_color_override? muzzle_flash_color_override : color) + QDEL_IN(thing, duration) + if(impacting && impact_type && duration > 0) + var/datum/point/p = beam_segments[beam_segments[beam_segments.len]] + var/atom/movable/thing = new impact_type + p.move_atom_to_src(thing) + var/matrix/M = new + M.Turn(Angle) + thing.transform = M + thing.color = color + thing.set_light(impact_light_range, impact_light_intensity, impact_light_color_override? impact_light_color_override : color) + QDEL_IN(thing, duration) + if(cleanup) + cleanup_beam_segments() - return 1 +//Returns true if the target atom is on our current turf and above the right layer +/obj/item/projectile/proc/can_hit_target(atom/target, var/list/passthrough) + return (target && ((target.layer >= TABLE_LAYER) || ismob(target)) && (loc == get_turf(target)) && (!(target in passthrough))) -/obj/item/projectile/Bump(atom/A as mob|obj|turf|area, forced=0) - if(A == src) - return 0 //no +/obj/item/projectile/Bump(atom/A) + if(A in permutated) + return FALSE + if(firer && !reflected) + if(A == firer || (A == firer.loc && istype(A, /obj/mecha))) //cannot shoot yourself or your mech + trajectory_ignore_forcemove = TRUE + forceMove(get_turf(A)) + trajectory_ignore_forcemove = FALSE + return FALSE - if(A == firer) - loc = A.loc - return 0 //cannot shoot yourself + var/distance = get_dist(starting, get_turf(src)) + var/turf/target_turf = get_turf(A) + var/passthrough = FALSE - if((bumped && !forced) || (A in permutated)) - return 0 - - var/passthrough = 0 //if the projectile should continue flying - var/distance = get_dist(starting,loc) - - bumped = 1 if(ismob(A)) var/mob/M = A if(istype(A, /mob/living)) @@ -246,13 +492,13 @@ var/shield_chance = min(80, (30 * (M.mob_size / 10))) //Small mobs have a harder time keeping a dead body as a shield than a human-sized one. Unathi would have an easier job, if they are made to be SIZE_LARGE in the future. -Mech if(prob(shield_chance)) visible_message("\The [M] uses [G.affecting] as a shield!") - if(Bump(G.affecting, forced=1)) + if(Bump(G.affecting)) return else visible_message("\The [M] tries to use [G.affecting] as a shield, but fails!") else visible_message("\The [M] uses [G.affecting] as a shield!") - if(Bump(G.affecting, forced=1)) + if(Bump(G.affecting)) return //If Bump() returns 0 (keep going) then we continue on to attack M. passthrough = !attack_mob(M, distance) @@ -269,119 +515,83 @@ //penetrating projectiles can pass through things that otherwise would not let them if(!passthrough && penetrating > 0) if(check_penetrate(A)) - passthrough = 1 + passthrough = TRUE penetrating-- - //the bullet passes through a dense object! if(passthrough) - //move ourselves onto A so we can continue on our way. - if(A) - if(istype(A, /turf)) - loc = A - else - loc = A.loc - permutated.Add(A) - bumped = 0 //reset bumped variable! - return 0 - - //stop flying - on_impact(A) - - density = 0 - invisibility = 101 + trajectory_ignore_forcemove = TRUE + forceMove(target_turf) + permutated.Add(A) + trajectory_ignore_forcemove = FALSE + return FALSE + if(A) + on_impact(A) qdel(src) + return TRUE + +//TODO: make it so this is called more reliably, instead of sometimes by bullet_act() and sometimes not +/obj/item/projectile/proc/on_hit(atom/target, blocked = 0, def_zone) + if(blocked >= 100) return 0//Full block + if(!isliving(target)) return 0 +// if(isanimal(target)) return 0 + var/mob/living/L = target + L.apply_effects(stun, weaken, paralyze, irradiate, stutter, eyeblur, drowsy, agony, blocked, incendiary, flammability) // add in AGONY! + if(modifier_type_to_apply) + L.add_modifier(modifier_type_to_apply, modifier_duration) return 1 -/obj/item/projectile/ex_act() - return //explosions probably shouldn't delete projectiles +//called when the projectile stops flying because it Bump'd with something +/obj/item/projectile/proc/on_impact(atom/A) + if(damage && damage_type == BURN) + var/turf/T = get_turf(A) + if(T) + T.hotspot_expose(700, 5) -/obj/item/projectile/CanPass(atom/movable/mover, turf/target, height=0, air_group=0) +//Checks if the projectile is eligible for embedding. Not that it necessarily will. +/obj/item/projectile/proc/can_embed() + //embed must be enabled and damage type must be brute + if(embed_chance == 0 || damage_type != BRUTE) + return 0 return 1 -/obj/item/projectile/process() - var/first_step = 1 +/obj/item/projectile/proc/get_structure_damage() + if(damage_type == BRUTE || damage_type == BURN) + return damage + return 0 - spawn while(src && src.loc) - if(kill_count-- < 1) - on_impact(src.loc) //for any final impact behaviours - qdel(src) - return - if((!( current ) || loc == current)) - current = locate(min(max(x + xo, 1), world.maxx), min(max(y + yo, 1), world.maxy), z) - if((x == 1 || x == world.maxx || y == 1 || y == world.maxy)) - qdel(src) - return +//return 1 if the projectile should be allowed to pass through after all, 0 if not. +/obj/item/projectile/proc/check_penetrate(atom/A) + return 1 - trajectory.increment() // increment the current location - location = trajectory.return_location(location) // update the locally stored location data - update_light() //energy projectiles will look glowy and fun +/obj/item/projectile/proc/check_fire(atom/target as mob, mob/living/user as mob) //Checks if you can hit them or not. + check_trajectory(target, user, pass_flags, flags) - if(!location) - qdel(src) // if it's left the world... kill it - return +/obj/item/projectile/CanPass() + return TRUE - if (is_below_sound_pressure(get_turf(src)) && !vacuum_traversal) //Deletes projectiles that aren't supposed to bein vacuum if they leave pressurised areas - qdel(src) - return +//Called when the projectile intercepts a mob. Returns 1 if the projectile hit the mob, 0 if it missed and should keep flying. +/obj/item/projectile/proc/attack_mob(mob/living/target_mob, distance, miss_modifier = 0) + if(!istype(target_mob)) + return - before_move() - Move(location.return_turf()) - after_move() + //roll to-hit + miss_modifier = max(15*(distance-2) - accuracy + miss_modifier + target_mob.get_evasion(), 0) + var/hit_zone = get_zone_with_miss_chance(def_zone, target_mob, miss_modifier, ranged_attack=(distance > 1 || original != target_mob)) //if the projectile hits a target we weren't originally aiming at then retain the chance to miss - if(!bumped && !isturf(original)) - if(loc == get_turf(original)) - if(!(original in permutated)) - if(Bump(original)) - return + var/result = PROJECTILE_FORCE_MISS + if(hit_zone) + def_zone = hit_zone //set def_zone, so if the projectile ends up hitting someone else later (to be implemented), it is more likely to hit the same part + result = target_mob.bullet_act(src, def_zone) - if(first_step) - muzzle_effect(effect_transform) - first_step = 0 - else if(!bumped) - tracer_effect(effect_transform) + if(result == PROJECTILE_FORCE_MISS) + if(!silenced) + visible_message("\The [src] misses [target_mob] narrowly!") + return FALSE - if(incendiary >= 2) //This should cover the bases of 'Why is there fuel here?' in a much cleaner way than previous. - if(src && src.loc) //Safety. - if(!src.loc.density) - var/trail_volume = (flammability * 0.20) - new /obj/effect/decal/cleanable/liquid_fuel/flamethrower_fuel(src.loc, trail_volume, src.dir) - - if(!hitscan) - sleep(step_delay) //add delay between movement iterations if it's not a hitscan weapon - -/obj/item/projectile/proc/before_move() - return - -/obj/item/projectile/proc/after_move() - return - -/obj/item/projectile/proc/setup_trajectory(turf/startloc, turf/targloc, var/x_offset = 0, var/y_offset = 0) - // setup projectile state - starting = startloc - current = startloc - yo = targloc.y - startloc.y + y_offset - xo = targloc.x - startloc.x + x_offset - - // trajectory dispersion - var/offset = 0 - if(dispersion) - var/radius = round(dispersion*9, 1) - offset = rand(-radius, radius) - - // plot the initial trajectory - trajectory = new() - trajectory.setup(starting, original, pixel_x, pixel_y, angle_offset=offset) - - // generate this now since all visual effects the projectile makes can use it - effect_transform = new() - effect_transform.Scale(trajectory.return_hypotenuse(), 1) - effect_transform.Turn(-trajectory.return_angle()) //no idea why this has to be inverted, but it works - - transform = turn(transform, -(trajectory.return_angle() + 90)) //no idea why 90 needs to be added, but it works - -/obj/item/projectile/proc/muzzle_effect(var/matrix/T) + //hit messages if(silenced) +<<<<<<< HEAD return if(ispath(muzzle_type)) @@ -455,13 +665,22 @@ var/turf/targloc = get_turf(target) if(!curloc || !targloc) return 0 +======= + to_chat(target_mob, "You've been hit in the [parse_zone(def_zone)] by \the [src]!") + else + visible_message("\The [target_mob] is hit by \the [src] in the [parse_zone(def_zone)]!")//X has fired Y is now given by the guns so you cant tell who shot you if you could not see the shooter +>>>>>>> 9ff8103... Merge pull request #5636 from kevinz000/pixel_projectiles - original = target + //admin logs + if(!no_attack_log) + if(istype(firer, /mob) && istype(target_mob)) + add_attack_logs(firer,target_mob,"Shot with \a [src.type] projectile") - //plot the initial trajectory - setup_trajectory(curloc, targloc) - return process(targloc) + //sometimes bullet_act() will want the projectile to continue flying + if (result == PROJECTILE_CONTINUE) + return FALSE +<<<<<<< HEAD /obj/item/projectile/test/process(var/turf/targloc) while(src) //Loop on through! if(result) @@ -469,12 +688,20 @@ // return (result - 1) if((!( targloc ) || loc == targloc)) targloc = locate(min(max(x + xo, 1), world.maxx), min(max(y + yo, 1), world.maxy), z) //Finding the target turf at map edge +======= + return TRUE +>>>>>>> 9ff8103... Merge pull request #5636 from kevinz000/pixel_projectiles - trajectory.increment() // increment the current location - location = trajectory.return_location(location) // update the locally stored location data - Move(location.return_turf()) +/obj/item/projectile/proc/launch_projectile(atom/target, target_zone, mob/user, params, angle_override, forced_spread = 0) + original = target + def_zone = check_zone(target_zone) + firer = user + var/direct_target + if(get_turf(target) == get_turf(src)) + direct_target = target +<<<<<<< HEAD var/mob/living/M = locate() in get_turf(src) if(istype(M)) //If there is someting living... result_ref = M @@ -489,14 +716,15 @@ /proc/check_trajectory(atom/target as mob|obj, atom/firer as mob|obj, var/pass_flags=PASSTABLE|PASSGLASS|PASSGRILLE, flags=null) if(!istype(target) || !istype(firer)) return 0 +======= + preparePixelProjectile(target, user? user : get_turf(src), params, forced_spread) + return fire(angle_override, direct_target) +>>>>>>> 9ff8103... Merge pull request #5636 from kevinz000/pixel_projectiles - var/obj/item/projectile/test/trace = new /obj/item/projectile/test(get_turf(firer)) //Making the test.... +//called to launch a projectile from a gun +/obj/item/projectile/proc/launch_from_gun(atom/target, target_zone, mob/user, params, angle_override, forced_spread, obj/item/weapon/gun/launcher) - //Set the flags and pass flags to that of the real projectile... - if(!isnull(flags)) - trace.flags = flags - trace.pass_flags = pass_flags + shot_from = launcher.name + silenced = launcher.silenced - var/output = trace.launch(target) //Test it! - qdel(trace) //No need for it anymore - return output //Send it back to the gun! + return launch_projectile(target, target_zone, user, params, angle_override, forced_spread) diff --git a/code/modules/projectiles/projectile/arc.dm b/code/modules/projectiles/projectile/arc.dm index ee12f4c8fb..ef61558518 100644 --- a/code/modules/projectiles/projectile/arc.dm +++ b/code/modules/projectiles/projectile/arc.dm @@ -9,12 +9,16 @@ /obj/item/projectile/arc name = "arcing shot" icon_state = "fireball" // WIP - step_delay = 2 // Travel a bit slower, to really sell the arc visuals. + speed = 2 // Travel a bit slower, to really sell the arc visuals. + movement_type = UNSTOPPABLE plane = ABOVE_PLANE // Since projectiles are 'in the air', they might visually overlap mobs while in flight, so the projectile needs to be above their plane. var/target_distance = null // How many tiles the impact site is. var/fired_dir = null // Which direction was the projectile fired towards. Needed to invert the projectile turning based on if facing left or right. var/obj/effect/projectile_shadow/shadow = null // Visual indicator for the projectile's 'true' position. Needed due to being bound to two dimensions in reality. +/obj/item/projectile/arc/Bump() + return + /obj/item/projectile/arc/Initialize() shadow = new(get_turf(src)) return ..() @@ -23,33 +27,32 @@ QDEL_NULL(shadow) return ..() -/obj/item/projectile/arc/Bump(atom/A, forced=0) - return 0 -// if(get_turf(src) != original) -// return 0 -// else -// return ..() - // This is a test projectile in the sense that its testing the code to make sure it works, // as opposed to a 'can I hit this thing' projectile. /obj/item/projectile/arc/test/on_impact(turf/T) new /obj/effect/explosion(T) return ..() -/obj/item/projectile/arc/launch(atom/target, target_zone, x_offset=0, y_offset=0, angle_offset=0) - var/expected_distance = get_dist(target, loc) - kill_count = expected_distance // So the projectile "hits the ground." +/obj/item/projectile/arc/old_style_target(target, source) + var/source_loc = get_turf(source) || get_turf(src) + var/expected_distance = get_dist(target, source_loc) + range = expected_distance // So the projectile "hits the ground." target_distance = expected_distance - fired_dir = get_dir(loc, target) - ..() // Does the regular launching stuff. + fired_dir = get_dir(source_loc, target) + ..() if(fired_dir & EAST) transform = turn(transform, -45) else if(fired_dir & WEST) transform = turn(transform, 45) +/obj/item/projectile/arc/on_range() + on_impact(loc) + return ..() // Visuals. /obj/item/projectile/arc/after_move() + if(QDELETED(src)) + return // Handle projectile turning in flight. // This won't turn if fired north/south, as it looks weird. var/turn_per_step = 90 / target_distance @@ -73,7 +76,7 @@ var/projectile_position = arc_progress / target_distance var/sine_position = projectile_position * 180 var/pixel_z_position = arc_max_height * sin(sine_position) - animate(src, pixel_z = pixel_z_position, time = step_delay) + animate(src, pixel_z = pixel_z_position, time = speed) // Update our shadow. shadow.forceMove(loc) diff --git a/code/modules/projectiles/projectile/beams.dm b/code/modules/projectiles/projectile/beams.dm index 59c05eddcc..0afec25187 100644 --- a/code/modules/projectiles/projectile/beams.dm +++ b/code/modules/projectiles/projectile/beams.dm @@ -15,9 +15,9 @@ light_power = 0.5 light_color = "#FF0D00" - muzzle_type = /obj/effect/projectile/laser/muzzle - tracer_type = /obj/effect/projectile/laser/tracer - impact_type = /obj/effect/projectile/laser/impact + muzzle_type = /obj/effect/projectile/muzzle/laser + tracer_type = /obj/effect/projectile/tracer/laser + impact_type = /obj/effect/projectile/impact/laser /obj/item/projectile/beam/practice name = "laser" @@ -54,9 +54,9 @@ light_power = 1 light_color = "#FF0D00" - muzzle_type = /obj/effect/projectile/laser_heavy/muzzle - tracer_type = /obj/effect/projectile/laser_heavy/tracer - impact_type = /obj/effect/projectile/laser_heavy/impact + muzzle_type = /obj/effect/projectile/muzzle/laser_heavy + tracer_type = /obj/effect/projectile/tracer/laser_heavy + impact_type = /obj/effect/projectile/impact/laser_heavy /obj/item/projectile/beam/heavylaser/fakeemitter name = "emitter beam" @@ -64,9 +64,9 @@ fire_sound = 'sound/weapons/emitter.ogg' light_color = "#00CC33" - muzzle_type = /obj/effect/projectile/emitter/muzzle - tracer_type = /obj/effect/projectile/emitter/tracer - impact_type = /obj/effect/projectile/emitter/impact + muzzle_type = /obj/effect/projectile/muzzle/emitter + tracer_type = /obj/effect/projectile/tracer/emitter + impact_type = /obj/effect/projectile/impact/emitter /obj/item/projectile/beam/heavylaser/cannon damage = 80 @@ -81,9 +81,9 @@ armor_penetration = 50 light_color = "#00CC33" - muzzle_type = /obj/effect/projectile/xray/muzzle - tracer_type = /obj/effect/projectile/xray/tracer - impact_type = /obj/effect/projectile/xray/impact + muzzle_type = /obj/effect/projectile/muzzle/xray + tracer_type = /obj/effect/projectile/tracer/xray + impact_type = /obj/effect/projectile/impact/xray /obj/item/projectile/beam/cyan name = "cyan beam" @@ -91,9 +91,9 @@ damage = 40 light_color = "#00C6FF" - muzzle_type = /obj/effect/projectile/laser_omni/muzzle - tracer_type = /obj/effect/projectile/laser_omni/tracer - impact_type = /obj/effect/projectile/laser_omni/impact + muzzle_type = /obj/effect/projectile/muzzle/laser_omni + tracer_type = /obj/effect/projectile/tracer/laser_omni + impact_type = /obj/effect/projectile/impact/laser_omni /obj/item/projectile/beam/pulse name = "pulse" @@ -103,9 +103,9 @@ armor_penetration = 100 light_color = "#0066FF" - muzzle_type = /obj/effect/projectile/laser_pulse/muzzle - tracer_type = /obj/effect/projectile/laser_pulse/tracer - impact_type = /obj/effect/projectile/laser_pulse/impact + muzzle_type = /obj/effect/projectile/muzzle/laser_pulse + tracer_type = /obj/effect/projectile/tracer/laser_pulse + impact_type = /obj/effect/projectile/impact/laser_pulse /obj/item/projectile/beam/pulse/on_hit(var/atom/target, var/blocked = 0) if(isturf(target)) @@ -119,9 +119,9 @@ damage = 0 // The actual damage is computed in /code/modules/power/singularity/emitter.dm light_color = "#00CC33" - muzzle_type = /obj/effect/projectile/emitter/muzzle - tracer_type = /obj/effect/projectile/emitter/tracer - impact_type = /obj/effect/projectile/emitter/impact + muzzle_type = /obj/effect/projectile/muzzle/emitter + tracer_type = /obj/effect/projectile/tracer/emitter + impact_type = /obj/effect/projectile/impact/emitter /obj/item/projectile/beam/lastertag/blue name = "lasertag beam" @@ -134,9 +134,9 @@ combustion = FALSE - muzzle_type = /obj/effect/projectile/laser_blue/muzzle - tracer_type = /obj/effect/projectile/laser_blue/tracer - impact_type = /obj/effect/projectile/laser_blue/impact + muzzle_type = /obj/effect/projectile/muzzle/laser_blue + tracer_type = /obj/effect/projectile/tracer/laser_blue + impact_type = /obj/effect/projectile/impact/laser_blue /obj/item/projectile/beam/lastertag/blue/on_hit(var/atom/target, var/blocked = 0) if(istype(target, /mob/living/carbon/human)) @@ -173,9 +173,9 @@ combustion = FALSE - muzzle_type = /obj/effect/projectile/laser_omni/muzzle - tracer_type = /obj/effect/projectile/laser_omni/tracer - impact_type = /obj/effect/projectile/laser_omni/impact + muzzle_type = /obj/effect/projectile/muzzle/laser_omni + tracer_type = /obj/effect/projectile/tracer/laser_omni + impact_type = /obj/effect/projectile/impact/laser_omni /obj/item/projectile/beam/lastertag/omni/on_hit(var/atom/target, var/blocked = 0) if(istype(target, /mob/living/carbon/human)) @@ -192,9 +192,9 @@ armor_penetration = 10 light_color = "#00CC33" - muzzle_type = /obj/effect/projectile/xray/muzzle - tracer_type = /obj/effect/projectile/xray/tracer - impact_type = /obj/effect/projectile/xray/impact + muzzle_type = /obj/effect/projectile/muzzle/xray + tracer_type = /obj/effect/projectile/tracer/xray + impact_type = /obj/effect/projectile/impact/xray /obj/item/projectile/beam/stun name = "stun beam" @@ -208,9 +208,9 @@ combustion = FALSE - muzzle_type = /obj/effect/projectile/stun/muzzle - tracer_type = /obj/effect/projectile/stun/tracer - impact_type = /obj/effect/projectile/stun/impact + muzzle_type = /obj/effect/projectile/muzzle/stun + tracer_type = /obj/effect/projectile/tracer/stun + impact_type = /obj/effect/projectile/impact/stun /obj/item/projectile/beam/stun/weak name = "weak stun beam" diff --git a/code/modules/projectiles/projectile/bullets.dm b/code/modules/projectiles/projectile/bullets.dm index cff515e403..8220e90eee 100644 --- a/code/modules/projectiles/projectile/bullets.dm +++ b/code/modules/projectiles/projectile/bullets.dm @@ -10,7 +10,7 @@ sharp = 1 var/mob_passthrough_check = 0 - muzzle_type = /obj/effect/projectile/bullet/muzzle + muzzle_type = /obj/effect/projectile/muzzle/bullet /obj/item/projectile/bullet/on_hit(var/atom/target, var/blocked = 0) if (..(target, blocked)) @@ -257,12 +257,12 @@ incendiary = 2 flammability = 4 agony = 30 - kill_count = 4 + range = 4 vacuum_traversal = 0 /obj/item/projectile/bullet/incendiary/flamethrower/large damage = 15 - kill_count = 6 + range = 6 /* Practice rounds and blanks */ diff --git a/code/modules/projectiles/projectile/energy.dm b/code/modules/projectiles/projectile/energy.dm index 2819e5510f..fcbc10f109 100644 --- a/code/modules/projectiles/projectile/energy.dm +++ b/code/modules/projectiles/projectile/energy.dm @@ -12,7 +12,7 @@ icon_state = "bullet" fire_sound = 'sound/weapons/gunshot_pathetic.ogg' damage = 5 - kill_count = 15 //if the shell hasn't hit anything after travelling this far it just explodes. + range = 15 //if the shell hasn't hit anything after travelling this far it just explodes. var/flash_range = 0 var/brightness = 7 var/light_colour = "#ffffff" @@ -165,7 +165,7 @@ icon_state = "plasma_stun" fire_sound = 'sound/weapons/blaster.ogg' armor_penetration = 10 - kill_count = 4 + range = 4 damage = 5 agony = 55 damage_type = BURN @@ -212,25 +212,25 @@ light_color = "#0000FF" embed_chance = 0 - muzzle_type = /obj/effect/projectile/pulse/muzzle + muzzle_type = /obj/effect/projectile/muzzle/pulse /obj/item/projectile/energy/phase name = "phase wave" icon_state = "phase" - kill_count = 6 + range = 6 damage = 5 SA_bonus_damage = 45 // 50 total on animals SA_vulnerability = SA_ANIMAL /obj/item/projectile/energy/phase/light - kill_count = 4 + range = 4 SA_bonus_damage = 35 // 40 total on animals /obj/item/projectile/energy/phase/heavy - kill_count = 8 + range = 8 SA_bonus_damage = 55 // 60 total on animals /obj/item/projectile/energy/phase/heavy/cannon - kill_count = 10 + range = 10 damage = 15 SA_bonus_damage = 60 // 75 total on animals diff --git a/code/modules/projectiles/projectile/hook.dm b/code/modules/projectiles/projectile/hook.dm index dd8096ecdd..ad02dee8cf 100644 --- a/code/modules/projectiles/projectile/hook.dm +++ b/code/modules/projectiles/projectile/hook.dm @@ -9,7 +9,7 @@ var/beam_state = "b_beam" damage = 5 - step_delay = 2 + speed = 2 damage_type = BURN check_armour = "energy" armor_penetration = 15 @@ -27,9 +27,9 @@ var/list/help_messages = list("slaps", "pokes", "nudges", "bumps", "pinches") var/done_mob_unique = FALSE // Has the projectile already done something to a mob? -/obj/item/projectile/energy/hook/launch(atom/target, target_zone, x_offset=0, y_offset=0, angle_offset=0) +/obj/item/projectile/energy/hook/launch_projectile(atom/target, target_zone, mob/user, params, angle_override, forced_spread = 0) var/expected_distance = get_dist(target, loc) - kill_count = expected_distance // So the hook hits the ground if no mob is hit. + range = expected_distance // So the hook hits the ground if no mob is hit. target_distance = expected_distance if(firer) // Needed to ensure later checks in impact and on hit function. launcher_intent = firer.a_intent diff --git a/code/modules/projectiles/projectile/magnetic.dm b/code/modules/projectiles/projectile/magnetic.dm index 1d8182aaea..9bb3fcb1f4 100644 --- a/code/modules/projectiles/projectile/magnetic.dm +++ b/code/modules/projectiles/projectile/magnetic.dm @@ -34,7 +34,7 @@ penetrating = 2 embed_chance = 0 armor_penetration = 40 - kill_count = 20 + range = 20 var/searing = 0 //Does this fuelrod ignore shields? var/detonate_travel = 0 //Will this fuelrod explode when it reaches maximum distance? @@ -50,7 +50,7 @@ if(energetic_impact) var/eye_coverage = 0 - for(var/mob/living/carbon/M in viewers(world.view, location)) + for(var/mob/living/carbon/M in viewers(world.view, get_turf(src))) eye_coverage = 0 if(iscarbon(M)) eye_coverage = M.eyecheck() @@ -103,7 +103,7 @@ armor_penetration = 100 penetrating = 100 //Theoretically, this shouldn't stop flying for a while, unless someone lines it up with a wall or fires it into a mountain. irradiate = 120 - kill_count = 75 + range = 75 searing = 1 detonate_travel = 1 detonate_mob = 1 @@ -116,6 +116,9 @@ return ..(target, blocked, def_zone) /obj/item/projectile/bullet/magnetic/fuelrod/supermatter/check_penetrate() +<<<<<<< HEAD + return 1 +======= return 1 /obj/item/projectile/bullet/magnetic/bore @@ -127,7 +130,7 @@ penetrating = 0 check_armour = "melee" irradiate = 20 - kill_count = 6 + range = 6 /obj/item/projectile/bullet/magnetic/bore/Bump(atom/A, forced=0) if(istype(A, /turf/simulated/mineral)) @@ -142,3 +145,4 @@ return 1 else ..() +>>>>>>> 9ff8103... Merge pull request #5636 from kevinz000/pixel_projectiles diff --git a/code/modules/projectiles/projectile/pellets.dm b/code/modules/projectiles/projectile/pellets.dm index 9b7ee244e3..306ab81268 100644 --- a/code/modules/projectiles/projectile/pellets.dm +++ b/code/modules/projectiles/projectile/pellets.dm @@ -9,10 +9,6 @@ var/base_spread = 90 //lower means the pellets spread more across body parts. If zero then this is considered a shrapnel explosion instead of a shrapnel cone var/spread_step = 10 //higher means the pellets spread more across body parts with distance -/obj/item/projectile/bullet/pellet/Bumped() - . = ..() - bumped = 0 //can hit all mobs in a tile. pellets is decremented inside attack_mob so this should be fine. - /obj/item/projectile/bullet/pellet/proc/get_pellets(var/distance) var/pellet_loss = round((distance - 1)/range_step) //pellets lost due to distance return max(pellets - pellet_loss, 1) diff --git a/code/modules/projectiles/projectile/special.dm b/code/modules/projectiles/projectile/special.dm index 2437081e3f..705a817236 100644 --- a/code/modules/projectiles/projectile/special.dm +++ b/code/modules/projectiles/projectile/special.dm @@ -219,7 +219,7 @@ embed_chance = 0 // nope nodamage = 1 damage_type = HALLOSS - muzzle_type = /obj/effect/projectile/bullet/muzzle + muzzle_type = /obj/effect/projectile/muzzle/bullet /obj/item/projectile/bola name = "bola" diff --git a/code/modules/projectiles/projectile/trace.dm b/code/modules/projectiles/projectile/trace.dm new file mode 100644 index 0000000000..3052d11ab7 --- /dev/null +++ b/code/modules/projectiles/projectile/trace.dm @@ -0,0 +1,38 @@ +//Helper proc to check if you can hit them or not. +/proc/check_trajectory(atom/target as mob|obj, atom/firer as mob|obj, var/pass_flags=PASSTABLE|PASSGLASS|PASSGRILLE, flags=null) + if(!istype(target) || !istype(firer)) + return 0 + + var/obj/item/projectile/test/trace = new /obj/item/projectile/test(get_turf(firer)) //Making the test.... + + //Set the flags and pass flags to that of the real projectile... + if(!isnull(flags)) + trace.flags = flags + trace.pass_flags = pass_flags + + return trace.launch_projectile(target) //Test it! + +/obj/item/projectile/proc/_check_fire(atom/target as mob, var/mob/living/user as mob) //Checks if you can hit them or not. + check_trajectory(target, user, pass_flags, flags) + +//"Tracing" projectile +/obj/item/projectile/test //Used to see if you can hit them. + invisibility = 101 //Nope! Can't see me! + hitscan = TRUE + nodamage = TRUE + damage = 0 + var/list/hit = list() + +/obj/item/projectile/test/process_hitscan() + . = ..() + if(!QDELING(src)) + qdel(src) + return hit + +/obj/item/projectile/test/Bump(atom/A) + if(A != src) + hit |= A + return ..() + +/obj/item/projectile/test/attack_mob() + return diff --git a/code/modules/recycling/disposal.dm b/code/modules/recycling/disposal.dm index 10157157fa..7dcca19a7b 100644 --- a/code/modules/recycling/disposal.dm +++ b/code/modules/recycling/disposal.dm @@ -467,7 +467,7 @@ H.vent_gas(loc) qdel(H) -/obj/machinery/disposal/CanPass(atom/movable/mover, turf/target, height=0, air_group=0) +/obj/machinery/disposal/CanPass(atom/movable/mover, turf/target, height, air_group) if(istype(mover, /obj/item/projectile)) return 1 if (istype(mover,/obj/item) && mover.throwing) diff --git a/code/modules/spells/spell_projectile.dm b/code/modules/spells/spell_projectile.dm index 07adda0081..01e7d163fa 100644 --- a/code/modules/spells/spell_projectile.dm +++ b/code/modules/spells/spell_projectile.dm @@ -7,7 +7,7 @@ var/spell/targeted/projectile/carried penetrating = 0 - kill_count = 10 //set by the duration of the spell + range = 10 //set by the duration of the spell var/proj_trail = 0 //if it leaves a trail var/proj_trail_lifespan = 0 //deciseconds diff --git a/code/modules/spells/targeted/projectile/projectile.dm b/code/modules/spells/targeted/projectile/projectile.dm index fef039d191..7181d3c140 100644 --- a/code/modules/spells/targeted/projectile/projectile.dm +++ b/code/modules/spells/targeted/projectile/projectile.dm @@ -29,12 +29,13 @@ If the spell_projectile is seeking, it will update its target every process and projectile.shot_from = user //fired from the user projectile.hitscan = !proj_step_delay - projectile.step_delay = proj_step_delay + projectile.speed = proj_step_delay if(istype(projectile, /obj/item/projectile/spell_projectile)) var/obj/item/projectile/spell_projectile/SP = projectile SP.carried = src //casting is magical - projectile.launch(target, target_zone="chest") - return + projectile.def_zone = check_zone("chest") + projectile.old_style_target(target) + projectile.fire() /spell/targeted/projectile/proc/choose_prox_targets(mob/user = usr, var/atom/movable/spell_holder) var/list/targets = list() diff --git a/code/modules/tables/interactions.dm b/code/modules/tables/interactions.dm index 19764b2997..f3b953ac25 100644 --- a/code/modules/tables/interactions.dm +++ b/code/modules/tables/interactions.dm @@ -1,5 +1,5 @@ -/obj/structure/table/CanPass(atom/movable/mover, turf/target, height=0, air_group=0) +/obj/structure/table/CanPass(atom/movable/mover, turf/target, height, air_group) if(air_group || (height==0)) return 1 if(istype(mover,/obj/item/projectile)) return (check_cover(mover,target)) diff --git a/code/modules/turbolift/turbolift_map.dm b/code/modules/turbolift/turbolift_map.dm index d812f5bf3e..44082654c6 100644 --- a/code/modules/turbolift/turbolift_map.dm +++ b/code/modules/turbolift/turbolift_map.dm @@ -32,7 +32,7 @@ var/uy = y var/uz = z var/udir = dir - forceMove(null) + moveToNullspace() // These modifiers are used in relation to the origin // to place the system control panels and doors. diff --git a/code/modules/xenobio/items/weapons.dm b/code/modules/xenobio/items/weapons.dm index b4894e4588..647ac7e081 100644 --- a/code/modules/xenobio/items/weapons.dm +++ b/code/modules/xenobio/items/weapons.dm @@ -90,9 +90,9 @@ // Probably for the best so that it doesn't harm the slime. taser_effect = FALSE - muzzle_type = /obj/effect/projectile/laser_omni/muzzle - tracer_type = /obj/effect/projectile/laser_omni/tracer - impact_type = /obj/effect/projectile/laser_omni/impact + muzzle_type = /obj/effect/projectile/muzzle/laser_omni + tracer_type = /obj/effect/projectile/tracer/laser_omni + impact_type = /obj/effect/projectile/impact/laser_omni /obj/item/projectile/beam/stun/xeno/weak //Weaker variant for non-research equipment, turrets, or rapid fire types. agony = 3 diff --git a/code/world.dm b/code/world.dm index 531be66ba6..598a4e02e8 100644 --- a/code/world.dm +++ b/code/world.dm @@ -1,3 +1,4 @@ +<<<<<<< HEAD /* The initialization of the game happens roughly like this: @@ -27,12 +28,16 @@ var/global/datum/global_init/init = new () global.init = null return 2 // QDEL_HINT_IWILLGC +======= +//Global init and the rest of world's code have been moved to code/global_init.dm and code/game/world.dm respectively. +>>>>>>> 9ff8103... Merge pull request #5636 from kevinz000/pixel_projectiles /world mob = /mob/new_player turf = /turf/space area = /area/space view = "15x15" cache_lifespan = 7 +<<<<<<< HEAD @@ -679,3 +684,5 @@ proc/establish_old_db_connection() max_z_changed() #undef FAILED_DB_CONNECTION_CUTOFF +======= +>>>>>>> 9ff8103... Merge pull request #5636 from kevinz000/pixel_projectiles diff --git a/icons/obj/projectiles_impact.dmi b/icons/obj/projectiles_impact.dmi new file mode 100644 index 0000000000000000000000000000000000000000..fd9d314310c8b656ccc80dd23b8e3ba8523d8b8c GIT binary patch literal 20181 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 literal 0 HcmV?d00001 diff --git a/icons/obj/projectiles_muzzle.dmi b/icons/obj/projectiles_muzzle.dmi new file mode 100644 index 0000000000000000000000000000000000000000..d88b9a283e47d4e15739e3d3f45d4ffed06d576e GIT binary patch literal 20583 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 O4y-Ngj+U9ZQT`3>=7Tl> literal 0 HcmV?d00001 diff --git a/vorestation.dme b/vorestation.dme index a0380b973e..0e08750e00 100644 --- a/vorestation.dme +++ b/vorestation.dme @@ -16,7 +16,11 @@ #include "code\_map_tests.dm" #include "code\_unit_tests.dm" #include "code\global.dm" +<<<<<<< HEAD:vorestation.dme #include "code\global_vr.dm" +======= +#include "code\global_init.dm" +>>>>>>> 9ff8103... Merge pull request #5636 from kevinz000/pixel_projectiles:polaris.dme #include "code\hub.dm" #include "code\names.dm" #include "code\stylesheet.dm" @@ -89,6 +93,7 @@ #include "code\_global_vars\sensitive.dm" #include "code\_global_vars\lists\mapping.dm" #include "code\_helpers\_global_objects.dm" +#include "code\_helpers\_lists.dm" #include "code\_helpers\atmospherics.dm" #include "code\_helpers\events.dm" #include "code\_helpers\files.dm" @@ -96,8 +101,11 @@ #include "code\_helpers\global_lists.dm" #include "code\_helpers\global_lists_vr.dm" #include "code\_helpers\icons.dm" +<<<<<<< HEAD:vorestation.dme #include "code\_helpers\icons_vr.dm" #include "code\_helpers\lists.dm" +======= +>>>>>>> 9ff8103... Merge pull request #5636 from kevinz000/pixel_projectiles:polaris.dme #include "code\_helpers\logging.dm" #include "code\_helpers\logging_vr.dm" #include "code\_helpers\matrices.dm" @@ -112,8 +120,12 @@ #include "code\_helpers\type2type.dm" #include "code\_helpers\type2type_vr.dm" #include "code\_helpers\unsorted.dm" +<<<<<<< HEAD:vorestation.dme #include "code\_helpers\unsorted_vr.dm" #include "code\_helpers\vector.dm" +======= +#include "code\_helpers\view.dm" +>>>>>>> 9ff8103... Merge pull request #5636 from kevinz000/pixel_projectiles:polaris.dme #include "code\_helpers\sorts\__main.dm" #include "code\_helpers\sorts\comparators.dm" #include "code\_helpers\sorts\TimSort.dm" @@ -241,6 +253,7 @@ #include "code\controllers\subsystems\processing\fastprocess.dm" #include "code\controllers\subsystems\processing\obj.dm" #include "code\controllers\subsystems\processing\processing.dm" +#include "code\controllers\subsystems\processing\projectiles.dm" #include "code\controllers\subsystems\processing\turfs.dm" #include "code\datums\ai_law_sets.dm" #include "code\datums\ai_laws.dm" @@ -261,6 +274,7 @@ #include "code\datums\mutable_appearance.dm" #include "code\datums\orbit.dm" #include "code\datums\organs.dm" +#include "code\datums\position_point_vector.dm" #include "code\datums\progressbar.dm" #include "code\datums\recipe.dm" #include "code\datums\riding.dm" @@ -430,6 +444,7 @@ #include "code\game\skincmd.dm" #include "code\game\sound.dm" #include "code\game\trader_visit.dm" +#include "code\game\world.dm" #include "code\game\antagonist\_antagonist_setup.dm" #include "code\game\antagonist\antagonist.dm" #include "code\game\antagonist\antagonist_add.dm" @@ -943,7 +958,11 @@ #include "code\game\objects\effects\spawners\bombspawner.dm" #include "code\game\objects\effects\spawners\gibspawner.dm" #include "code\game\objects\effects\temporary_visuals\miscellaneous.dm" -#include "code\game\objects\effects\temporary_visuals\temproary_visual.dm" +#include "code\game\objects\effects\temporary_visuals\temporary_visual.dm" +#include "code\game\objects\effects\temporary_visuals\projectiles\impact.dm" +#include "code\game\objects\effects\temporary_visuals\projectiles\muzzle.dm" +#include "code\game\objects\effects\temporary_visuals\projectiles\projectile_effects.dm" +#include "code\game\objects\effects\temporary_visuals\projectiles\tracer.dm" #include "code\game\objects\items\antag_spawners.dm" #include "code\game\objects\items\apc_frame.dm" #include "code\game\objects\items\blueprints.dm" @@ -2595,7 +2614,6 @@ #include "code\modules\power\tesla\tesla_act.dm" #include "code\modules\projectiles\ammunition.dm" #include "code\modules\projectiles\dnalocking.dm" -#include "code\modules\projectiles\effects.dm" #include "code\modules\projectiles\gun.dm" #include "code\modules\projectiles\projectile.dm" #include "code\modules\projectiles\ammunition\magazines.dm" @@ -2657,6 +2675,7 @@ #include "code\modules\projectiles\projectile\magnetic.dm" #include "code\modules\projectiles\projectile\pellets.dm" #include "code\modules\projectiles\projectile\special.dm" +#include "code\modules\projectiles\projectile\trace.dm" #include "code\modules\projectiles\targeting\targeting_client.dm" #include "code\modules\projectiles\targeting\targeting_gun.dm" #include "code\modules\projectiles\targeting\targeting_mob.dm"