Merge branch 'master' of https://github.com/Citadel-Station-13/Citadel-Station-13 into Ghommie-cit726
This commit is contained in:
@@ -764,7 +764,6 @@ GLOBAL_DATUM_INIT(sdql2_vv_statobj, /obj/effect/statclick/SDQL2_VV_all, new(null
|
||||
for(var/arg in arguments)
|
||||
new_args[++new_args.len] = SDQL_expression(source, arg)
|
||||
if(object == GLOB) // Global proc.
|
||||
procname = "/proc/[procname]"
|
||||
return superuser? (call(procname)(new_args)) : (WrapAdminProcCall(GLOBAL_PROC, procname, new_args))
|
||||
return superuser? (call(object, procname)(new_args)) : (WrapAdminProcCall(object, procname, new_args))
|
||||
|
||||
|
||||
@@ -705,10 +705,17 @@
|
||||
var/list/names = list()
|
||||
names += "---- Space Ruins ----"
|
||||
for(var/name in SSmapping.space_ruins_templates)
|
||||
names[name] = list(SSmapping.space_ruins_templates[name], ZTRAIT_SPACE_RUINS, /area/space)
|
||||
names[name] = list(SSmapping.space_ruins_templates[name], ZTRAIT_SPACE_RUINS, list(/area/space))
|
||||
names += "---- Lava Ruins ----"
|
||||
for(var/name in SSmapping.lava_ruins_templates)
|
||||
names[name] = list(SSmapping.lava_ruins_templates[name], ZTRAIT_LAVA_RUINS, /area/lavaland/surface/outdoors/unexplored)
|
||||
names[name] = list(SSmapping.lava_ruins_templates[name], ZTRAIT_LAVA_RUINS, list(/area/lavaland/surface/outdoors/unexplored))
|
||||
names += "---- Ice Ruins ----"
|
||||
for(var/name in SSmapping.ice_ruins_templates)
|
||||
names[name] = list(SSmapping.ice_ruins_templates[name], ZTRAIT_ICE_RUINS, list(/area/icemoon/surface/outdoors/unexplored, /area/icemoon/underground/unexplored))
|
||||
names += "---- Ice Underground Ruins ----"
|
||||
for(var/name in SSmapping.ice_ruins_underground_templates)
|
||||
names[name] = list(SSmapping.ice_ruins_underground_templates[name], ZTRAIT_ICE_RUINS_UNDERGROUND, list(/area/icemoon/underground/unexplored))
|
||||
|
||||
var/ruinname = input("Select ruin", "Spawn Ruin") as null|anything in names
|
||||
var/data = names[ruinname]
|
||||
|
||||
@@ -18,9 +18,12 @@
|
||||
var/image/item = image('icons/turf/overlays.dmi',S,"greenOverlay")
|
||||
item.plane = ABOVE_LIGHTING_PLANE
|
||||
preview += item
|
||||
var/list/orientations = list("South" = SOUTH, "North" = NORTH, "East" = EAST, "West" = WEST)
|
||||
var/choice = input(src, "Which orientation? Maps are normally facing SOUTH.", "Template Orientation", "South") as null|anything in orientations
|
||||
var/orientation = orientations[choice]
|
||||
images += preview
|
||||
if(alert(src,"Confirm location.","Template Confirm","Yes","No") == "Yes")
|
||||
if(template.load(T, centered = TRUE))
|
||||
if(template.load(T, centered = TRUE, orientation = orientation))
|
||||
message_admins("<span class='adminnotice'>[key_name_admin(src)] has placed a map template ([template.name]) at [ADMIN_COORDJMP(T)]</span>")
|
||||
else
|
||||
to_chat(src, "Failed to place map")
|
||||
|
||||
@@ -27,6 +27,9 @@
|
||||
for(var/obj/effect/landmark/carpspawn/L in GLOB.landmarks_list)
|
||||
if(isturf(L.loc))
|
||||
spawn_locs += L.loc
|
||||
for(var/obj/effect/landmark/loneopspawn/L in GLOB.landmarks_list)
|
||||
if(isturf(L.loc))
|
||||
spawn_locs += L.loc
|
||||
|
||||
if(!spawn_locs)
|
||||
message_admins("No valid spawn locations found, aborting...")
|
||||
|
||||
@@ -90,7 +90,7 @@
|
||||
. = TRUE
|
||||
|
||||
update_icon()
|
||||
|
||||
|
||||
/obj/item/assembly/signaler/attackby(obj/item/W, mob/user, params)
|
||||
if(issignaler(W))
|
||||
var/obj/item/assembly/signaler/signaler2 = W
|
||||
@@ -162,7 +162,6 @@
|
||||
return
|
||||
return ..(signal)
|
||||
|
||||
|
||||
// Embedded signaller used in anomalies.
|
||||
/obj/item/assembly/signaler/anomaly
|
||||
name = "anomaly core"
|
||||
@@ -179,12 +178,53 @@
|
||||
return FALSE
|
||||
if(signal.data["code"] != code)
|
||||
return FALSE
|
||||
if(suicider)
|
||||
manual_suicide(suicider)
|
||||
for(var/obj/effect/anomaly/A in get_turf(src))
|
||||
A.anomalyNeutralize()
|
||||
return TRUE
|
||||
|
||||
/obj/item/assembly/signaler/anomaly/attack_self()
|
||||
return
|
||||
/obj/item/assembly/signaler/anomaly/manual_suicide(mob/living/carbon/user)
|
||||
user.visible_message("<span class='suicide'>[user]'s [src] is reacting to the radio signal, warping [user.p_their()] body!</span>")
|
||||
user.suiciding = TRUE
|
||||
user.suicide_log()
|
||||
user.gib()
|
||||
|
||||
/obj/item/assembly/signaler/anomaly/attackby(obj/item/I, mob/user, params)
|
||||
if(I.tool_behaviour == TOOL_ANALYZER)
|
||||
to_chat(user, "<span class='notice'>Analyzing... [src]'s stabilized field is fluctuating along frequency [format_frequency(frequency)], code [code].</span>")
|
||||
..()
|
||||
|
||||
//Anomaly cores
|
||||
/obj/item/assembly/signaler/anomaly/pyro
|
||||
name = "\improper pyroclastic anomaly core"
|
||||
desc = "The neutralized core of a pyroclastic anomaly. It feels warm to the touch. It'd probably be valuable for research."
|
||||
icon_state = "pyro core"
|
||||
anomaly_type = /obj/effect/anomaly/pyro
|
||||
|
||||
/obj/item/assembly/signaler/anomaly/grav
|
||||
name = "\improper gravitational anomaly core"
|
||||
desc = "The neutralized core of a gravitational anomaly. It feels much heavier than it looks. It'd probably be valuable for research."
|
||||
icon_state = "grav core"
|
||||
anomaly_type = /obj/effect/anomaly/grav
|
||||
|
||||
/obj/item/assembly/signaler/anomaly/flux
|
||||
name = "\improper flux anomaly core"
|
||||
desc = "The neutralized core of a flux anomaly. Touching it makes your skin tingle. It'd probably be valuable for research."
|
||||
icon_state = "flux core"
|
||||
anomaly_type = /obj/effect/anomaly/flux
|
||||
|
||||
/obj/item/assembly/signaler/anomaly/bluespace
|
||||
name = "\improper bluespace anomaly core"
|
||||
desc = "The neutralized core of a bluespace anomaly. It keeps phasing in and out of view. It'd probably be valuable for research."
|
||||
icon_state = "anomaly core"
|
||||
anomaly_type = /obj/effect/anomaly/bluespace
|
||||
|
||||
/obj/item/assembly/signaler/anomaly/vortex
|
||||
name = "\improper vortex anomaly core"
|
||||
desc = "The neutralized core of a vortex anomaly. It won't sit still, as if some invisible force is acting on it. It'd probably be valuable for research."
|
||||
icon_state = "vortex core"
|
||||
anomaly_type = /obj/effect/anomaly/bhole
|
||||
|
||||
/obj/item/assembly/signaler/cyborg
|
||||
|
||||
|
||||
@@ -28,6 +28,8 @@
|
||||
|
||||
/obj/machinery/atmospherics/pipe/manifold/update_icon()
|
||||
cut_overlays()
|
||||
if(!center)
|
||||
center = mutable_appearance(icon, "manifold_center")
|
||||
PIPING_LAYER_DOUBLE_SHIFT(center, piping_layer)
|
||||
add_overlay(center)
|
||||
|
||||
|
||||
@@ -26,6 +26,8 @@
|
||||
|
||||
/obj/machinery/atmospherics/pipe/manifold4w/update_icon()
|
||||
cut_overlays()
|
||||
if(!center)
|
||||
center = mutable_appearance(icon, "manifold_center")
|
||||
PIPING_LAYER_DOUBLE_SHIFT(center, piping_layer)
|
||||
add_overlay(center)
|
||||
|
||||
|
||||
@@ -76,19 +76,3 @@
|
||||
/datum/export/manifest_correct_denied/get_cost(obj/O)
|
||||
var/obj/item/paper/fluff/jobs/cargo/manifest/M = O
|
||||
return ..() - M.order_cost
|
||||
|
||||
// Paper work done correctly
|
||||
|
||||
/datum/export/paperwork_correct
|
||||
cost = 120 // finicky number 20 x 120 = 2400 per crate
|
||||
k_elasticity = 0
|
||||
unit_name = "correct paperwork"
|
||||
export_types = list(/obj/item/folder/paperwork_correct)
|
||||
|
||||
// Paper work not done retruned
|
||||
|
||||
/datum/export/paperwork_incorrect
|
||||
cost = -500 // Failed to meet NT standers
|
||||
k_elasticity = 0
|
||||
unit_name = "returned incorrect paperwork"
|
||||
export_types = list(/obj/item/folder/paperwork)
|
||||
|
||||
@@ -148,15 +148,7 @@
|
||||
crate_name = "supermatter shard crate"
|
||||
crate_type = /obj/structure/closet/crate/secure/engineering
|
||||
dangerous = TRUE
|
||||
|
||||
/datum/supply_pack/engine/supermatter_spray
|
||||
name = "Supermatter Spray Crate"
|
||||
desc = "The single thing that can truly heal the supermatter."
|
||||
cost = 2000
|
||||
contains = list(/obj/item/supermatterspray)
|
||||
crate_name = "supermatter shard crate"
|
||||
crate_type = /obj/structure/closet/crate/engineering/electrical
|
||||
|
||||
|
||||
/datum/supply_pack/engine/tesla_coils
|
||||
name = "Tesla Coil Crate"
|
||||
desc = "Whether it's high-voltage executions, creating research points, or just plain old power generation: This pack of four Tesla coils can do it all!"
|
||||
|
||||
@@ -129,14 +129,6 @@
|
||||
/obj/item/rcd_ammo)
|
||||
crate_name = "rcd ammo"
|
||||
|
||||
/datum/supply_pack/materials/loom
|
||||
name = "Loom"
|
||||
desc = "A large pre-made loom."
|
||||
cost = 1000
|
||||
contains = list(/obj/structure/loom/unanchored)
|
||||
crate_name = "loom crate"
|
||||
crate_type = /obj/structure/closet/crate/large
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
///////////////////////////// Canisters //////////////////////////////////////
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@@ -87,37 +87,12 @@
|
||||
crate_type = /obj/structure/closet/crate/wooden
|
||||
crate_name = "calligraphy crate"
|
||||
|
||||
/datum/supply_pack/misc/paper_work
|
||||
name = "Freelance Paper work"
|
||||
desc = "The Nanotrasen Primary Bureaucratic Database Intelligence (PDBI) reports that the station has not completed its funding and grant paperwork this solar cycle. In order to gain further funding, your station is required to fill out (10) ten of these forms or no additional capital will be disbursed. We have sent you ten copies of the following form and we expect every one to be up to Nanotrasen Standards." // Disbursement. It's not a typo, look it up.
|
||||
cost = 700 // Net of 0 credits but makes (120 x 10 = 1200)
|
||||
contains = list(/obj/item/folder/paperwork,
|
||||
/obj/item/pen/fountain
|
||||
)
|
||||
crate_name = "Paperwork"
|
||||
|
||||
/datum/supply_pack/misc/paper_work/generate()
|
||||
. = ..()
|
||||
for(var/i in 1 to 9)
|
||||
new /obj/item/folder/paperwork(.)
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
//////////////////////////////// Entertainment ///////////////////////////////
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/datum/supply_pack/misc/randombedsheets
|
||||
name = "Bedsheet Crate (R)"
|
||||
desc = "Snuggle up in some sweet sheets with this assorted bedsheet crate. Each set comes with eight random bedsheets for your slumbering pleasure!"
|
||||
cost = 2000
|
||||
contains = list(/obj/item/bedsheet/random)
|
||||
crate_name = "random bedsheet crate"
|
||||
|
||||
/datum/supply_pack/misc/randombedsheets/generate()
|
||||
. = ..()
|
||||
for(var/i in 1 to 7)
|
||||
new /obj/item/bedsheet/random(.)
|
||||
|
||||
/datum/supply_pack/misc/coloredsheets
|
||||
name = "Bedsheet Crate (C)"
|
||||
name = "Bedsheet Crate"
|
||||
desc = "Give your night life a splash of color with this crate filled with bedsheets! Contains a total of nine different-colored sheets."
|
||||
cost = 1250
|
||||
contains = list(/obj/item/bedsheet/blue,
|
||||
|
||||
@@ -94,8 +94,6 @@ GLOBAL_LIST_EMPTY(preferences_datums)
|
||||
var/skin_tone = "caucasian1" //Skin color
|
||||
var/use_custom_skin_tone = FALSE
|
||||
var/eye_color = "000" //Eye color
|
||||
var/horn_color = "85615a" //Horn color
|
||||
var/wing_color = "fff" //Wing color
|
||||
var/datum/species/pref_species = new /datum/species/human() //Mutant race
|
||||
var/list/features = list("mcolor" = "FFF",
|
||||
"mcolor2" = "FFF",
|
||||
@@ -104,8 +102,10 @@ GLOBAL_LIST_EMPTY(preferences_datums)
|
||||
"tail_human" = "None",
|
||||
"snout" = "Round",
|
||||
"horns" = "None",
|
||||
"horns_color" = "85615a",
|
||||
"ears" = "None",
|
||||
"wings" = "None",
|
||||
"wings_color" = "FFF",
|
||||
"frills" = "None",
|
||||
"deco_wings" = "None",
|
||||
"spines" = "None",
|
||||
@@ -496,7 +496,7 @@ GLOBAL_LIST_EMPTY(preferences_datums)
|
||||
dat += "<h3>Horns</h3>"
|
||||
|
||||
dat += "<a style='display:block;width:100px' href='?_src_=prefs;preference=horns;task=input'>[features["horns"]]</a>"
|
||||
dat += "<span style='border:1px solid #161616; background-color: #[horn_color];'> </span> <a href='?_src_=prefs;preference=horns_color;task=input'>Change</a><BR>"
|
||||
dat += "<span style='border:1px solid #161616; background-color: #[features["horns_color"]];'> </span> <a href='?_src_=prefs;preference=horns_color;task=input'>Change</a><BR>"
|
||||
|
||||
|
||||
mutant_category++
|
||||
@@ -609,7 +609,7 @@ GLOBAL_LIST_EMPTY(preferences_datums)
|
||||
dat += "<h3>Decorative wings</h3>"
|
||||
|
||||
dat += "<a style='display:block;width:100px' href='?_src_=prefs;preference=deco_wings;task=input'>[features["deco_wings"]]</a>"
|
||||
dat += "<span style='border:1px solid #161616; background-color: #[wing_color];'> </span> <a href='?_src_=prefs;preference=wings_color;task=input'>Change</a><BR>"
|
||||
dat += "<span style='border:1px solid #161616; background-color: #[features["wings_color"]];'> </span> <a href='?_src_=prefs;preference=wings_color;task=input'>Change</a><BR>"
|
||||
|
||||
if(pref_species.mutant_bodyparts["insect_wings"])
|
||||
if(!mutant_category)
|
||||
@@ -618,7 +618,7 @@ GLOBAL_LIST_EMPTY(preferences_datums)
|
||||
dat += "<h3>Insect wings</h3>"
|
||||
|
||||
dat += "<a style='display:block;width:100px' href='?_src_=prefs;preference=insect_wings;task=input'>[features["insect_wings"]]</a>"
|
||||
dat += "<span style='border:1px solid #161616; background-color: #[wing_color];'> </span> <a href='?_src_=prefs;preference=wings_color;task=input'>Change</a><BR>"
|
||||
dat += "<span style='border:1px solid #161616; background-color: #[features["wings_color"]];'> </span> <a href='?_src_=prefs;preference=wings_color;task=input'>Change</a><BR>"
|
||||
mutant_category++
|
||||
if(mutant_category >= MAX_MUTANT_ROWS)
|
||||
dat += "</td>"
|
||||
@@ -1770,12 +1770,12 @@ GLOBAL_LIST_EMPTY(preferences_datums)
|
||||
features["horns"] = new_horns
|
||||
|
||||
if("horns_color")
|
||||
var/new_horn_color = input(user, "Choose your character's horn colour:", "Character Preference","#"+horn_color) as color|null
|
||||
var/new_horn_color = input(user, "Choose your character's horn colour:", "Character Preference","#"+features["horns_color"]) as color|null
|
||||
if(new_horn_color)
|
||||
if (new_horn_color == "#000000")
|
||||
horn_color = "#85615A"
|
||||
features["horns_color"] = "85615A"
|
||||
else
|
||||
horn_color = sanitize_hexcolor(new_horn_color)
|
||||
features["horns_color"] = sanitize_hexcolor(new_horn_color)
|
||||
|
||||
if("wings")
|
||||
var/new_wings
|
||||
@@ -1784,12 +1784,12 @@ GLOBAL_LIST_EMPTY(preferences_datums)
|
||||
features["wings"] = new_wings
|
||||
|
||||
if("wings_color")
|
||||
var/new_wing_color = input(user, "Choose your character's wing colour:", "Character Preference","#"+wing_color) as color|null
|
||||
var/new_wing_color = input(user, "Choose your character's wing colour:", "Character Preference","#"+features["wings_color"]) as color|null
|
||||
if(new_wing_color)
|
||||
if (new_wing_color == "#000000")
|
||||
wing_color = "#FFFFFF"
|
||||
features["wings_color"] = "#FFFFFF"
|
||||
else
|
||||
wing_color = sanitize_hexcolor(new_wing_color)
|
||||
features["wings_color"] = sanitize_hexcolor(new_wing_color)
|
||||
|
||||
if("frills")
|
||||
var/new_frills
|
||||
@@ -2459,9 +2459,6 @@ GLOBAL_LIST_EMPTY(preferences_datums)
|
||||
organ_eyes.old_eye_color = eye_color
|
||||
character.hair_color = hair_color
|
||||
character.facial_hair_color = facial_hair_color
|
||||
character.horn_color = horn_color
|
||||
character.wing_color = wing_color
|
||||
|
||||
character.skin_tone = skin_tone
|
||||
character.dna.skin_tone_override = use_custom_skin_tone ? skin_tone : null
|
||||
character.hair_style = hair_style
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
// You do not need to raise this if you are adding new values that have sane defaults.
|
||||
// Only raise this value when changing the meaning/format/name/layout of an existing value
|
||||
// where you would want the updater procs below to run
|
||||
#define SAVEFILE_VERSION_MAX 29
|
||||
#define SAVEFILE_VERSION_MAX 30
|
||||
|
||||
/*
|
||||
SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Carn
|
||||
@@ -181,7 +181,7 @@ SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Car
|
||||
if(lickable)
|
||||
ENABLE_BITFIELD(vore_flags,LICKABLE)
|
||||
|
||||
if(current_version < 29)
|
||||
if(current_version < 30)
|
||||
switch(features["taur"])
|
||||
if("Husky", "Lab", "Shepherd", "Fox", "Wolf")
|
||||
features["taur"] = "Canine"
|
||||
@@ -190,6 +190,10 @@ SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Car
|
||||
if("Cow")
|
||||
features["taur"] = "Cow (Spotted)"
|
||||
|
||||
if(current_version < 31)
|
||||
S["wing_color"] >> features["wings_color"]
|
||||
S["horn_color"] >> features["horns_color"]
|
||||
|
||||
/datum/preferences/proc/load_path(ckey,filename="preferences.sav")
|
||||
if(!ckey)
|
||||
return
|
||||
@@ -421,15 +425,6 @@ SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Car
|
||||
if(newtype)
|
||||
pref_species = new newtype
|
||||
|
||||
if(!S["features["mcolor"]"] || S["features["mcolor"]"] == "#000")
|
||||
WRITE_FILE(S["features["mcolor"]"] , "#FFF")
|
||||
|
||||
if(!S["features["horn_color"]"] || S["features["horn_color"]"] == "#000")
|
||||
WRITE_FILE(S["features["horn_color"]"] , "#85615a")
|
||||
|
||||
if(!S["features["wing_color"]"] || S["features["wing_color"]"] == "#000")
|
||||
WRITE_FILE(S["features["wing_color"]"] , "#FFF")
|
||||
|
||||
//Character
|
||||
S["real_name"] >> real_name
|
||||
S["nameless"] >> nameless
|
||||
@@ -453,8 +448,6 @@ SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Car
|
||||
S["shirt_color"] >> shirt_color
|
||||
S["socks"] >> socks
|
||||
S["socks_color"] >> socks_color
|
||||
S["horn_color"] >> horn_color
|
||||
S["wing_color"] >> wing_color
|
||||
S["backbag"] >> backbag
|
||||
S["jumpsuit_style"] >> jumpsuit_style
|
||||
S["uplink_loc"] >> uplink_spawn_loc
|
||||
@@ -564,15 +557,6 @@ SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Car
|
||||
if(!custom_names[custom_name_id])
|
||||
custom_names[custom_name_id] = get_default_name(custom_name_id)
|
||||
|
||||
if(!features["mcolor"] || features["mcolor"] == "#000")
|
||||
features["mcolor"] = pick("FFFFFF","7F7F7F", "7FFF7F", "7F7FFF", "FF7F7F", "7FFFFF", "FF7FFF", "FFFF7F")
|
||||
|
||||
if(!features["horn_color"] || features["horn_color"] == "#000")
|
||||
features["horn_color"] = "85615a"
|
||||
|
||||
if(!features["wing_color"] || features["wing_color"] == "#000")
|
||||
features["wing_color"] = "FFFFFF"
|
||||
|
||||
nameless = sanitize_integer(nameless, 0, 1, initial(nameless))
|
||||
be_random_name = sanitize_integer(be_random_name, 0, 1, initial(be_random_name))
|
||||
be_random_body = sanitize_integer(be_random_body, 0, 1, initial(be_random_body))
|
||||
@@ -599,8 +583,8 @@ SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Car
|
||||
else
|
||||
skin_tone = sanitize_inlist(skin_tone, GLOB.skin_tones - GLOB.nonstandard_skin_tones, initial(skin_tone))
|
||||
|
||||
horn_color = sanitize_hexcolor(horn_color, 3, FALSE)
|
||||
wing_color = sanitize_hexcolor(wing_color, 3, FALSE, "#FFFFFF")
|
||||
features["horns_color"] = sanitize_hexcolor(features["horns_color"], 3, FALSE, "85615a")
|
||||
features["wings_color"] = sanitize_hexcolor(features["wings_color"], 3, FALSE, "FFFFFF")
|
||||
backbag = sanitize_inlist(backbag, GLOB.backbaglist, initial(backbag))
|
||||
jumpsuit_style = sanitize_inlist(jumpsuit_style, GLOB.jumpsuitlist, initial(jumpsuit_style))
|
||||
uplink_spawn_loc = sanitize_inlist(uplink_spawn_loc, GLOB.uplink_spawn_loc_list, initial(uplink_spawn_loc))
|
||||
@@ -714,8 +698,8 @@ SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Car
|
||||
WRITE_FILE(S["shirt_color"] , shirt_color)
|
||||
WRITE_FILE(S["socks"] , socks)
|
||||
WRITE_FILE(S["socks_color"] , socks_color)
|
||||
WRITE_FILE(S["horn_color"] , horn_color)
|
||||
WRITE_FILE(S["wing_color"] , wing_color)
|
||||
WRITE_FILE(S["horns_color"] , features["horns_color"])
|
||||
WRITE_FILE(S["wings_color"] , features["wings_color"])
|
||||
WRITE_FILE(S["backbag"] , backbag)
|
||||
WRITE_FILE(S["jumpsuit_style"] , jumpsuit_style)
|
||||
WRITE_FILE(S["uplink_loc"] , uplink_spawn_loc)
|
||||
|
||||
@@ -143,6 +143,13 @@
|
||||
max_heat_protection_temperature = SHOES_MAX_TEMP_PROTECT
|
||||
pocket_storage_component_path = /datum/component/storage/concrete/pockets/shoes
|
||||
|
||||
/obj/item/clothing/shoes/winterboots/ice_boots
|
||||
name = "ice hiking boots"
|
||||
desc = "A pair of winter boots with special grips on the bottom, designed to prevent slipping on frozen surfaces."
|
||||
icon_state = "iceboots"
|
||||
item_state = "iceboots"
|
||||
clothing_flags = NOSLIP_ICE
|
||||
|
||||
/obj/item/clothing/shoes/winterboots/christmasbootsr
|
||||
name = "red christmas boots"
|
||||
desc = "A pair of fluffy red christmas boots!"
|
||||
|
||||
@@ -811,7 +811,7 @@
|
||||
desc = "A dusty button up winter coat. The zipper tab looks like a tiny pickaxe."
|
||||
icon_state = "coatminer"
|
||||
item_state = "coatminer"
|
||||
allowed = list(/obj/item/pickaxe, /obj/item/flashlight, /obj/item/tank/internals/emergency_oxygen, /obj/item/tank/internals/plasmaman, /obj/item/toy, /obj/item/storage/fancy/cigarettes, /obj/item/lighter)
|
||||
allowed = list(/obj/item/pickaxe, /obj/item/flashlight, /obj/item/tank/internals, /obj/item/resonator, /obj/item/mining_scanner, /obj/item/t_scanner/adv_mining_scanner, /obj/item/gun/energy/kinetic_accelerator, /obj/item/toy, /obj/item/storage/fancy/cigarettes, /obj/item/lighter)
|
||||
armor = list("melee" = 10, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 0)
|
||||
hoodtype = /obj/item/clothing/head/hooded/winterhood/miner
|
||||
|
||||
|
||||
@@ -19,6 +19,8 @@
|
||||
var/list/spawn_locs = list()
|
||||
for(var/obj/effect/landmark/carpspawn/L in GLOB.landmarks_list)
|
||||
spawn_locs += L.loc
|
||||
for(var/obj/effect/landmark/loneopspawn/L in GLOB.landmarks_list)
|
||||
spawn_locs += L.loc
|
||||
if(!spawn_locs.len)
|
||||
return MAP_ERROR
|
||||
|
||||
|
||||
@@ -58,6 +58,9 @@
|
||||
var/list/candidates = pollGhostCandidates("Do you wish to be considered for pirate crew?", ROLE_TRAITOR)
|
||||
shuffle_inplace(candidates)
|
||||
|
||||
if(!SSmapping.empty_space)
|
||||
SSmapping.empty_space = SSmapping.add_new_zlevel("Empty Area For Pirates", list(ZTRAIT_LINKAGE = SELFLOOPING))
|
||||
|
||||
var/datum/map_template/shuttle/pirate/default/ship = new
|
||||
var/x = rand(TRANSITIONEDGE,world.maxx - TRANSITIONEDGE - ship.width)
|
||||
var/y = rand(TRANSITIONEDGE,world.maxy - TRANSITIONEDGE - ship.height)
|
||||
|
||||
@@ -1,63 +0,0 @@
|
||||
dmm_suite{
|
||||
/*
|
||||
|
||||
dmm_suite version 1.0
|
||||
Released January 30th, 2011.
|
||||
|
||||
NOTE: Map saving functionality removed
|
||||
|
||||
defines the object /dmm_suite
|
||||
- Provides the proc load_map()
|
||||
- Loads the specified map file onto the specified z-level.
|
||||
- provides the proc write_map()
|
||||
- Returns a text string of the map in dmm format
|
||||
ready for output to a file.
|
||||
- provides the proc save_map()
|
||||
- Returns a .dmm file if map is saved
|
||||
- Returns FALSE if map fails to save
|
||||
|
||||
The dmm_suite provides saving and loading of map files in BYOND's native DMM map
|
||||
format. It approximates the map saving and loading processes of the Dream Maker
|
||||
and Dream Seeker programs so as to allow editing, saving, and loading of maps at
|
||||
runtime.
|
||||
|
||||
------------------------
|
||||
|
||||
To save a map at runtime, create an instance of /dmm_suite, and then call
|
||||
write_map(), which accepts three arguments:
|
||||
- A turf representing one corner of a three dimensional grid (Required).
|
||||
- Another turf representing the other corner of the same grid (Required).
|
||||
- Any, or a combination, of several bit flags (Optional, see documentation).
|
||||
|
||||
The order in which the turfs are supplied does not matter, the /dmm_writer will
|
||||
determine the grid containing both, in much the same way as DM's block() function.
|
||||
write_map() will then return a string representing the saved map in dmm format;
|
||||
this string can then be saved to a file, or used for any other purose.
|
||||
|
||||
------------------------
|
||||
|
||||
To load a map at runtime, create an instance of /dmm_suite, and then call load_map(),
|
||||
which accepts two arguments:
|
||||
- A .dmm file to load (Required).
|
||||
- A number representing the z-level on which to start loading the map (Optional).
|
||||
|
||||
The /dmm_suite will load the map file starting on the specified z-level. If no
|
||||
z-level was specified, world.maxz will be increased so as to fit the map. Note
|
||||
that if you wish to load a map onto a z-level that already has objects on it,
|
||||
you will have to handle the removal of those objects. Otherwise the new map will
|
||||
simply load the new objects on top of the old ones.
|
||||
|
||||
Also note that all type paths specified in the .dmm file must exist in the world's
|
||||
code, and that the /dmm_reader trusts that files to be loaded are in fact valid
|
||||
.dmm files. Errors in the .dmm format will cause runtime errors.
|
||||
|
||||
*/
|
||||
|
||||
verb/load_map(var/dmm_file as file, var/x_offset as num, var/y_offset as num, var/z_offset as num, var/cropMap as num, var/measureOnly as num, no_changeturf as num){
|
||||
// dmm_file: A .dmm file to load (Required).
|
||||
// z_offset: A number representing the z-level on which to start loading the map (Optional).
|
||||
// cropMap: When true, the map will be cropped to fit the existing world dimensions (Optional).
|
||||
// measureOnly: When true, no changes will be made to the world (Optional).
|
||||
// no_changeturf: When true, turf/AfterChange won't be called on loaded turfs
|
||||
}
|
||||
}
|
||||
196
code/modules/mapping/map_config.dm
Normal file
196
code/modules/mapping/map_config.dm
Normal file
@@ -0,0 +1,196 @@
|
||||
//used for holding information about unique properties of maps
|
||||
//feed it json files that match the datum layout
|
||||
//defaults to box
|
||||
// -Cyberboss
|
||||
|
||||
/datum/map_config
|
||||
// Metadata
|
||||
var/config_filename = "_maps/boxstation.json"
|
||||
var/defaulted = TRUE // set to FALSE by LoadConfig() succeeding
|
||||
// Config from maps.txt
|
||||
var/config_max_users = 0
|
||||
var/config_min_users = 0
|
||||
var/voteweight = 1
|
||||
var/max_round_search_span = 0 //If this is nonzero, then if the map has been played more than max_rounds_played within the search span (max determined by define in persistence.dm), this map won't be available.
|
||||
var/max_rounds_played = 0
|
||||
|
||||
// Config actually from the JSON - should default to Box
|
||||
var/map_name = "Box Station"
|
||||
var/map_path = "map_files/BoxStation"
|
||||
var/map_file = "BoxStation.dmm"
|
||||
|
||||
var/traits = null
|
||||
var/space_ruin_levels = 2
|
||||
var/space_empty_levels = 1
|
||||
var/station_ruin_budget = -1 // can be set to manually override the station ruins budget on maps that don't support station ruins, stopping the error from being unable to place the ruins.
|
||||
|
||||
var/minetype = "lavaland"
|
||||
|
||||
var/maptype = MAP_TYPE_STATION //This should be used to adjust ingame behavior depending on the specific type of map being played. For instance, if an overmap were added, it'd be appropriate for it to only generate with a MAP_TYPE_SHIP
|
||||
|
||||
var/announcertype = "standard" //Determines the announcer the map uses. standard uses the default announcer, classic, but has a random chance to use other similarly-themed announcers, like medibot
|
||||
|
||||
var/allow_custom_shuttles = TRUE
|
||||
var/shuttles = list(
|
||||
"cargo" = "cargo_box",
|
||||
"ferry" = "ferry_fancy",
|
||||
"whiteship" = "whiteship_box",
|
||||
"emergency" = "emergency_box")
|
||||
|
||||
var/year_offset = 540 //The offset of ingame year from the actual IRL year. You know you want to make a map that takes place in the 90's. Don't lie.
|
||||
|
||||
// "fun things"
|
||||
/// Orientation to load in by default.
|
||||
var/orientation = SOUTH //byond defaults to placing everyting SOUTH.
|
||||
|
||||
/proc/load_map_config(filename = "data/next_map.json", default_to_box, delete_after, error_if_missing = TRUE)
|
||||
var/datum/map_config/config = new
|
||||
if (default_to_box)
|
||||
return config
|
||||
if (!config.LoadConfig(filename, error_if_missing))
|
||||
qdel(config)
|
||||
config = new /datum/map_config // Fall back to Box
|
||||
if (delete_after)
|
||||
fdel(filename)
|
||||
return config
|
||||
|
||||
#define CHECK_EXISTS(X) if(!istext(json[X])) { log_world("[##X] missing from json!"); return; }
|
||||
/datum/map_config/proc/LoadConfig(filename, error_if_missing)
|
||||
if(!fexists(filename))
|
||||
if(error_if_missing)
|
||||
log_world("map_config not found: [filename]")
|
||||
return
|
||||
|
||||
var/json = file(filename)
|
||||
if(!json)
|
||||
log_world("Could not open map_config: [filename]")
|
||||
return
|
||||
|
||||
json = file2text(json)
|
||||
if(!json)
|
||||
log_world("map_config is not text: [filename]")
|
||||
return
|
||||
|
||||
json = json_decode(json)
|
||||
if(!json)
|
||||
log_world("map_config is not json: [filename]")
|
||||
return
|
||||
|
||||
config_filename = filename
|
||||
|
||||
CHECK_EXISTS("map_name")
|
||||
map_name = json["map_name"]
|
||||
CHECK_EXISTS("map_path")
|
||||
map_path = json["map_path"]
|
||||
|
||||
map_file = json["map_file"]
|
||||
// "map_file": "BoxStation.dmm"
|
||||
if (istext(map_file))
|
||||
if (!fexists("_maps/[map_path]/[map_file]"))
|
||||
log_world("Map file ([map_path]/[map_file]) does not exist!")
|
||||
return
|
||||
// "map_file": ["Lower.dmm", "Upper.dmm"]
|
||||
else if (islist(map_file))
|
||||
for (var/file in map_file)
|
||||
if (!fexists("_maps/[map_path]/[file]"))
|
||||
log_world("Map file ([map_path]/[file]) does not exist!")
|
||||
return
|
||||
else
|
||||
log_world("map_file missing from json!")
|
||||
return
|
||||
|
||||
if (islist(json["shuttles"]))
|
||||
var/list/L = json["shuttles"]
|
||||
for(var/key in L)
|
||||
var/value = L[key]
|
||||
shuttles[key] = value
|
||||
else if ("shuttles" in json)
|
||||
log_world("map_config shuttles is not a list!")
|
||||
return
|
||||
|
||||
traits = json["traits"]
|
||||
// "traits": [{"Linkage": "Cross"}, {"Space Ruins": true}]
|
||||
if (islist(traits))
|
||||
// "Station" is set by default, but it's assumed if you're setting
|
||||
// traits you want to customize which level is cross-linked
|
||||
for (var/level in traits)
|
||||
if (!(ZTRAIT_STATION in level))
|
||||
level[ZTRAIT_STATION] = TRUE
|
||||
// "traits": null or absent -> default
|
||||
else if (!isnull(traits))
|
||||
log_world("map_config traits is not a list!")
|
||||
return
|
||||
|
||||
var/temp = json["space_ruin_levels"]
|
||||
if (isnum(temp))
|
||||
space_ruin_levels = temp
|
||||
else if (!isnull(temp))
|
||||
log_world("map_config space_ruin_levels is not a number!")
|
||||
return
|
||||
|
||||
temp = json["space_empty_levels"]
|
||||
if (isnum(temp))
|
||||
space_empty_levels = temp
|
||||
else if (!isnull(temp))
|
||||
log_world("map_config space_empty_levels is not a number!")
|
||||
return
|
||||
|
||||
if("station_ruin_budget" in json)
|
||||
station_ruin_budget = json["station_ruin_budget"]
|
||||
|
||||
temp = json["year_offset"]
|
||||
if (isnum(temp))
|
||||
year_offset = temp
|
||||
else if (!isnull(temp))
|
||||
log_world("map_config year_offset is not a number!")
|
||||
return
|
||||
|
||||
if ("minetype" in json)
|
||||
minetype = json["minetype"]
|
||||
|
||||
if ("maptype" in json)
|
||||
maptype = json["maptype"]
|
||||
|
||||
if ("announcertype" in json)
|
||||
announcertype = json["announcertype"]
|
||||
|
||||
if ("orientation" in json)
|
||||
orientation = json["orientation"]
|
||||
if(!(orientation in GLOB.cardinals))
|
||||
orientation = SOUTH
|
||||
|
||||
allow_custom_shuttles = json["allow_custom_shuttles"] != FALSE
|
||||
|
||||
defaulted = FALSE
|
||||
return TRUE
|
||||
#undef CHECK_EXISTS
|
||||
|
||||
/datum/map_config/proc/GetFullMapPaths()
|
||||
if (istext(map_file))
|
||||
return list("_maps/[map_path]/[map_file]")
|
||||
. = list()
|
||||
for (var/file in map_file)
|
||||
. += "_maps/[map_path]/[file]"
|
||||
|
||||
/datum/map_config/proc/MakeNextMap()
|
||||
return config_filename == "data/next_map.json" || fcopy(config_filename, "data/next_map.json")
|
||||
|
||||
/// badmin moments. Keep up to date with LoadConfig()!
|
||||
/datum/map_config/proc/WriteNextMap()
|
||||
var/list/jsonlist = list()
|
||||
jsonlist["map_name"] = map_name
|
||||
jsonlist["map_path"] = map_path
|
||||
jsonlist["map_file"] = map_file
|
||||
jsonlist["shuttles"] = shuttles
|
||||
jsonlist["traits"] = traits
|
||||
jsonlist["space_ruin_levels"] = space_ruin_levels
|
||||
jsonlist["year_offset"] = year_offset
|
||||
jsonlist["minetype"] = minetype
|
||||
jsonlist["maptype"] = maptype
|
||||
jsonlist["announcertype"] = announcertype
|
||||
jsonlist["orientation"] = orientation
|
||||
jsonlist["allow_custom_shuttles"] = allow_custom_shuttles
|
||||
if(fexists("data/next_map.json"))
|
||||
fdel("data/next_map.json")
|
||||
var/F = file("data/next_map.json")
|
||||
WRITE_FILE(F, json_encode(jsonlist))
|
||||
46
code/modules/mapping/map_orientation_pattern.dm
Normal file
46
code/modules/mapping/map_orientation_pattern.dm
Normal file
@@ -0,0 +1,46 @@
|
||||
GLOBAL_LIST_INIT(map_orientation_patterns, list(
|
||||
TEXT_NORTH = new /datum/map_orientation_pattern/north,
|
||||
TEXT_SOUTH = new /datum/map_orientation_pattern/south,
|
||||
TEXT_EAST = new /datum/map_orientation_pattern/east,
|
||||
TEXT_WEST = new /datum/map_orientation_pattern/west
|
||||
))
|
||||
|
||||
/datum/map_orientation_pattern
|
||||
var/invert_x
|
||||
var/invert_y
|
||||
var/swap_xy
|
||||
var/xi
|
||||
var/yi
|
||||
var/turn_angle
|
||||
|
||||
/datum/map_orientation_pattern/north
|
||||
invert_y = TRUE
|
||||
invert_x = TRUE
|
||||
swap_xy = FALSE
|
||||
xi = -1
|
||||
yi = 1
|
||||
turn_angle = 180
|
||||
|
||||
/datum/map_orientation_pattern/south
|
||||
invert_y = FALSE
|
||||
invert_x = FALSE
|
||||
swap_xy = FALSE
|
||||
xi = 1
|
||||
yi = -1
|
||||
turn_angle = 0
|
||||
|
||||
/datum/map_orientation_pattern/east
|
||||
invert_y = TRUE
|
||||
invert_x = FALSE
|
||||
swap_xy = TRUE
|
||||
xi = 1
|
||||
yi = 1
|
||||
turn_angle = 90
|
||||
|
||||
/datum/map_orientation_pattern/west
|
||||
invert_y = FALSE
|
||||
invert_x = TRUE
|
||||
swap_xy = TRUE
|
||||
xi = -1
|
||||
yi = -1
|
||||
turn_angle = 270
|
||||
@@ -1,11 +1,14 @@
|
||||
/datum/map_template
|
||||
var/name = "Default Template Name"
|
||||
var/width = 0
|
||||
var/width = 0 //all these are for SOUTH!
|
||||
var/height = 0
|
||||
var/mappath = null
|
||||
var/zdepth = 1
|
||||
var/mappath
|
||||
var/loaded = 0 // Times loaded this round
|
||||
var/datum/parsed_map/cached_map
|
||||
var/keep_cached_map = FALSE
|
||||
var/default_annihilate = FALSE
|
||||
var/list/ztraits //zlevel traits for load_new_z
|
||||
|
||||
/datum/map_template/New(path = null, rename = null, cache = FALSE)
|
||||
if(path)
|
||||
@@ -15,16 +18,45 @@
|
||||
if(rename)
|
||||
name = rename
|
||||
|
||||
/datum/map_template/proc/preload_size(path, cache = FALSE)
|
||||
/datum/map_template/Destroy()
|
||||
QDEL_NULL(cached_map)
|
||||
return ..()
|
||||
|
||||
/datum/map_template/proc/preload_size(path = mappath, force_cache = FALSE)
|
||||
if(cached_map)
|
||||
return cached_map.parsed_bounds
|
||||
var/datum/parsed_map/parsed = new(file(path))
|
||||
var/bounds = parsed?.bounds
|
||||
var/bounds = parsed?.parsed_bounds
|
||||
if(bounds)
|
||||
width = bounds[MAP_MAXX] // Assumes all templates are rectangular, have a single Z level, and begin at 1,1,1
|
||||
height = bounds[MAP_MAXY]
|
||||
if(cache)
|
||||
width = bounds[MAP_MAXX] - bounds[MAP_MINX] + 1
|
||||
height = bounds[MAP_MAXY] - bounds[MAP_MINY] + 1
|
||||
zdepth = bounds[MAP_MAXZ] - bounds[MAP_MINZ] + 1
|
||||
if(force_cache || keep_cached_map)
|
||||
cached_map = parsed
|
||||
return bounds
|
||||
|
||||
/datum/map_template/proc/get_parsed_bounds()
|
||||
return preload_size(mappath)
|
||||
|
||||
/datum/map_template/proc/get_last_loaded_bounds()
|
||||
if(cached_map)
|
||||
return cached_map.bounds
|
||||
return get_parsed_bounds()
|
||||
|
||||
/datum/map_template/proc/get_last_loaded_turf_block()
|
||||
if(!cached_map)
|
||||
CRASH("Improper use of get_last_loaded_turf_block, no cached_map.")
|
||||
var/list/B = cached_map.bounds
|
||||
return block(locate(B[MAP_MINX], B[MAP_MINY], B[MAP_MINZ]), locate(B[MAP_MAXX], B[MAP_MAXY], B[MAP_MAXZ]))
|
||||
|
||||
/datum/map_template/proc/get_size(orientation = SOUTH)
|
||||
if(!width || !height || !zdepth)
|
||||
preload_size(mappath)
|
||||
var/rotate = (orientation & (NORTH|SOUTH)) != NONE
|
||||
if(rotate)
|
||||
return list(height, width, zdepth)
|
||||
return list(width, height, zdepth)
|
||||
|
||||
/datum/parsed_map/proc/initTemplateBounds()
|
||||
var/list/obj/machinery/atmospherics/atmos_machines = list()
|
||||
var/list/obj/structure/cable/cables = list()
|
||||
@@ -55,12 +87,12 @@
|
||||
SSmachines.setup_template_powernets(cables)
|
||||
SSair.setup_template_machinery(atmos_machines)
|
||||
|
||||
/datum/map_template/proc/load_new_z(list/traits = list(ZTRAIT_AWAY = TRUE))
|
||||
var/x = round((world.maxx - width)/2)
|
||||
var/y = round((world.maxy - height)/2)
|
||||
/datum/map_template/proc/load_new_z(orientation = SOUTH, list/ztraits = src.ztraits || list(ZTRAIT_AWAY = TRUE), centered = TRUE)
|
||||
var/x = centered? max(round((world.maxx - width) / 2), 1) : 1
|
||||
var/y = centered? max(round((world.maxy - height) / 2), 1) : 1
|
||||
|
||||
var/datum/space_level/level = SSmapping.add_new_zlevel(name, traits)
|
||||
var/datum/parsed_map/parsed = load_map(file(mappath), x, y, level.z_value, no_changeturf=(SSatoms.initialized == INITIALIZATION_INSSATOMS), placeOnTop=TRUE)
|
||||
var/datum/space_level/level = SSmapping.add_new_zlevel(name, ztraits)
|
||||
var/datum/parsed_map/parsed = load_map(file(mappath), x, y, level.z_value, no_changeturf=(SSatoms.initialized == INITIALIZATION_INSSATOMS), placeOnTop = TRUE, orientation = orientation)
|
||||
var/list/bounds = parsed.bounds
|
||||
if(!bounds)
|
||||
return FALSE
|
||||
@@ -71,31 +103,67 @@
|
||||
parsed.initTemplateBounds()
|
||||
smooth_zlevel(world.maxz)
|
||||
log_game("Z-level [name] loaded at [x],[y],[world.maxz]")
|
||||
on_map_loaded(world.maxz, parsed.bounds)
|
||||
|
||||
return level
|
||||
|
||||
/datum/map_template/proc/load(turf/T, centered = FALSE)
|
||||
//Override for custom behavior
|
||||
/datum/map_template/proc/on_map_loaded(z, list/bounds)
|
||||
loaded++
|
||||
|
||||
/**
|
||||
* Proc to trigger a load at a specific area. Calls on_map_loaded(T.z, loaded_bounds) afterwards.
|
||||
*
|
||||
* @params
|
||||
* * turf/T - Turf to load at
|
||||
* * centered - Center at T or load with the bottomright corner being at T
|
||||
* * orientation - SOUTH is default, anything else rotates the map to face it with the point of reference being the map itself is facing south by default. Cardinals only, don't be a 4head and put in multiple flags. It won't work or be pretty if you try.
|
||||
* * annihilate - Should we destroy stuff in our bounds while loading
|
||||
* * force_cache - Should we force the parsed shuttle to cache instead of being GC'd post loading if it wasn't going to be cached by default
|
||||
* * rotate_placement_to_orientation - Has no effect if centered. Should we rotate where we load it around the turf we're loading at? Used for stuff like engine submaps when the station is rotated.
|
||||
*
|
||||
*/
|
||||
/datum/map_template/proc/load(turf/T, centered = FALSE, orientation = SOUTH, annihilate = default_annihilate, force_cache = FALSE, rotate_placement_to_orientation = FALSE)
|
||||
var/old_T = T
|
||||
if(centered)
|
||||
T = locate(T.x - round(width/2) , T.y - round(height/2) , T.z)
|
||||
T = locate(T.x - round(((orientation & (NORTH|SOUTH))? width : height) / 2) , T.y - round(((orientation & (NORTH|SOUTH)) ? height : width) / 2) , T.z) // %180 catches East/West (90,270) rotations on true, North/South (0,180) rotations on false
|
||||
else if(rotate_placement_to_orientation && (orientation != SOUTH))
|
||||
var/newx = T.x
|
||||
var/newy = T.y
|
||||
if(orientation == NORTH)
|
||||
newx -= width
|
||||
newy -= height - 1
|
||||
else if(orientation == WEST)
|
||||
newy -= width
|
||||
else if(orientation == EAST)
|
||||
newx -= height - 1
|
||||
// eh let's not silently fail.
|
||||
if(!ISINRANGE(newx, 1, world.maxx) || !ISINRANGE(newy, 1, world.maxy))
|
||||
stack_trace("Warning: Rotation placed a map template load spot ([COORD(T)]) out of bounds of the game world. Clamping to world borders, this might cause issues.")
|
||||
T = locate(clamp(newx, 1, world.maxx), clamp(newy, 1, world.maxy), T.z)
|
||||
if(!T)
|
||||
return
|
||||
if(T.x+width > world.maxx)
|
||||
if(T.x+width-1 > world.maxx)
|
||||
return
|
||||
if(T.y+height > world.maxy)
|
||||
if(T.y+height-1 > world.maxy)
|
||||
return
|
||||
|
||||
var/list/border = block(locate(max(T.x-1, 1), max(T.y-1, 1), T.z),
|
||||
locate(min(T.x+width+1, world.maxx), min(T.y+height+1, world.maxy), T.z))
|
||||
for(var/L in border)
|
||||
var/turf/turf_to_disable = L
|
||||
var/list/border = block(locate(max(T.x - 1, 1), max(T.y - 1, 1), T.z),
|
||||
locate(min(T.x + width + 1, world.maxx), min(T.y + height + 1, world.maxy), T.z))
|
||||
for(var/i in border)
|
||||
var/turf/turf_to_disable = i
|
||||
SSair.remove_from_active(turf_to_disable) //stop processing turfs along the border to prevent runtimes, we return it in initTemplateBounds()
|
||||
turf_to_disable.atmos_adjacent_turfs?.Cut()
|
||||
|
||||
if(annihilate == MAP_TEMPLATE_ANNIHILATE_PRELOAD)
|
||||
annihilate_bounds(old_T, centered, orientation)
|
||||
|
||||
// Accept cached maps, but don't save them automatically - we don't want
|
||||
// ruins clogging up memory for the whole round.
|
||||
var/datum/parsed_map/parsed = cached_map || new(file(mappath))
|
||||
cached_map = keep_cached_map ? parsed : null
|
||||
if(!parsed.load(T.x, T.y, T.z, cropMap=TRUE, no_changeturf=(SSatoms.initialized == INITIALIZATION_INSSATOMS), placeOnTop=TRUE))
|
||||
var/is_cached = cached_map
|
||||
var/datum/parsed_map/parsed = is_cached || new(file(mappath))
|
||||
cached_map = (force_cache || keep_cached_map) ? parsed : is_cached
|
||||
if(!parsed.load(T.x, T.y, T.z, cropMap=TRUE, no_changeturf=(SSatoms.initialized == INITIALIZATION_INSSATOMS), placeOnTop=TRUE, orientation = orientation, annihilate_tiles = (annihilate == MAP_TEMPLATE_ANNIHILATE_LOADING)))
|
||||
return
|
||||
var/list/bounds = parsed.bounds
|
||||
if(!bounds)
|
||||
@@ -108,19 +176,36 @@
|
||||
parsed.initTemplateBounds()
|
||||
|
||||
log_game("[name] loaded at [T.x],[T.y],[T.z]")
|
||||
on_map_loaded(T.z, parsed.bounds)
|
||||
|
||||
return bounds
|
||||
|
||||
/datum/map_template/proc/get_affected_turfs(turf/T, centered = FALSE)
|
||||
var/turf/placement = T
|
||||
if(centered)
|
||||
var/turf/corner = locate(placement.x - round(width/2), placement.y - round(height/2), placement.z)
|
||||
if(corner)
|
||||
placement = corner
|
||||
return block(placement, locate(placement.x+width-1, placement.y+height-1, placement.z))
|
||||
|
||||
//This, get_affected_turfs, and load() calculations for bounds/center can probably be optimized. Later.
|
||||
/datum/map_template/proc/annihilate_bounds(turf/origin, centered = FALSE, orientation = SOUTH)
|
||||
var/deleted_atoms = 0
|
||||
log_world("Annihilating objects in map loading location.")
|
||||
var/list/turfs_to_clean = get_affected_turfs(origin, centered, orientation)
|
||||
if(turfs_to_clean.len)
|
||||
var/list/kill_these = list()
|
||||
for(var/i in turfs_to_clean)
|
||||
var/turf/T = i
|
||||
kill_these += T.contents
|
||||
for(var/i in kill_these)
|
||||
qdel(i)
|
||||
CHECK_TICK
|
||||
deleted_atoms++
|
||||
log_world("Annihilated [deleted_atoms] objects.")
|
||||
|
||||
//for your ever biggening badminnery kevinz000
|
||||
//❤ - Cyberboss
|
||||
/proc/load_new_z_level(file, name, list/traits)
|
||||
/proc/load_new_z_level(file, name, orientation, list/ztraits)
|
||||
var/datum/map_template/template = new(file, name)
|
||||
return template.load_new_z(traits)
|
||||
return template.load_new_z(orientation, ztraits)
|
||||
|
||||
/datum/map_template/proc/get_affected_turfs(turf/T, centered = FALSE, orientation = SOUTH)
|
||||
var/turf/placement = T
|
||||
if(centered)
|
||||
var/turf/corner = locate(placement.x - round(((orientation & (NORTH|SOUTH))? width : height) / 2), placement.y - round(((orientation & (NORTH|SOUTH))? height : width) / 2), placement.z) // %180 catches East/West (90,270) rotations on true, North/South (0,180) rotations on false
|
||||
if(corner)
|
||||
placement = corner
|
||||
return block(placement, locate(placement.x + ((orientation & (NORTH|SOUTH)) ? width : height) - 1, placement.y + ((orientation & (NORTH|SOUTH))? height : width) - 1, placement.z))
|
||||
|
||||
@@ -7,13 +7,21 @@ GLOBAL_DATUM_INIT(_preloader, /datum/map_preloader, new)
|
||||
parent_type = /datum
|
||||
var/list/attributes
|
||||
var/target_path
|
||||
var/turn_angle
|
||||
var/swap_x
|
||||
var/swap_y
|
||||
var/swap_xy
|
||||
|
||||
/world/proc/preloader_setup(list/the_attributes, path)
|
||||
if(the_attributes.len)
|
||||
/world/proc/preloader_setup(list/the_attributes, path, turn_angle, swap_x, swap_y, swap_xy)
|
||||
if(length(the_attributes) || turn_angle)
|
||||
GLOB.use_preloader = TRUE
|
||||
var/datum/map_preloader/preloader_local = GLOB._preloader
|
||||
preloader_local.attributes = the_attributes
|
||||
preloader_local.target_path = path
|
||||
preloader_local.turn_angle = turn_angle
|
||||
preloader_local.swap_x = swap_x
|
||||
preloader_local.swap_y = swap_y
|
||||
preloader_local.swap_xy = swap_xy
|
||||
|
||||
/world/proc/preloader_load(atom/what)
|
||||
GLOB.use_preloader = FALSE
|
||||
@@ -29,6 +37,21 @@ GLOBAL_DATUM_INIT(_preloader, /datum/map_preloader, new)
|
||||
GLOB.dirty_vars += message
|
||||
#endif
|
||||
what.vars[attribute] = value
|
||||
// handle post processing, so things like directions on subtypes don't break.
|
||||
if(preloader_local.turn_angle) //safe way to check for if this is necessary
|
||||
what.dir = turn(what.dir, preloader_local.turn_angle)
|
||||
var/px = what.pixel_x
|
||||
var/py = what.pixel_y
|
||||
if(preloader_local.swap_y) //same order of operations as the load rotation, mirror and then x/y swapping.
|
||||
py = -py
|
||||
if(preloader_local.swap_x)
|
||||
px = -px
|
||||
if(preloader_local.swap_xy)
|
||||
var/opx = px
|
||||
px = py
|
||||
py = opx
|
||||
what.pixel_x = px
|
||||
what.pixel_y = py
|
||||
|
||||
/area/template_noop
|
||||
name = "Area Passthrough"
|
||||
|
||||
@@ -19,9 +19,13 @@
|
||||
|
||||
/// Unoffset bounds. Null on parse failure.
|
||||
var/list/parsed_bounds
|
||||
var/width
|
||||
var/height
|
||||
/// Offset bounds. Same as parsed_bounds until load().
|
||||
var/list/bounds
|
||||
|
||||
var/datum/map_template/template_host
|
||||
|
||||
// raw strings used to represent regexes more accurately
|
||||
// '' used to avoid confusing syntax highlighting
|
||||
var/static/regex/dmmRegex = new(@'"([a-zA-Z]+)" = \(((?:.|\n)*?)\)\n(?!\t)|\((\d+),(\d+),(\d+)\) = \{"([a-zA-Z\n]*)"\}', "g")
|
||||
@@ -41,14 +45,33 @@
|
||||
/// - `no_changeturf`: When true, [turf/AfterChange] won't be called on loaded turfs
|
||||
/// - `x_lower`, `x_upper`, `y_lower`, `y_upper`: Coordinates (relative to the map) to crop to (Optional).
|
||||
/// - `placeOnTop`: Whether to use [turf/PlaceOnTop] rather than [turf/ChangeTurf] (Optional).
|
||||
/proc/load_map(dmm_file as file, x_offset as num, y_offset as num, z_offset as num, cropMap as num, measureOnly as num, no_changeturf as num, x_lower = -INFINITY as num, x_upper = INFINITY as num, y_lower = -INFINITY as num, y_upper = INFINITY as num, placeOnTop = FALSE as num)
|
||||
var/datum/parsed_map/parsed = new(dmm_file, x_lower, x_upper, y_lower, y_upper, measureOnly)
|
||||
/proc/load_map(
|
||||
dmm_file as file,
|
||||
x_offset as num,
|
||||
y_offset as num,
|
||||
z_offset as num,
|
||||
cropMap as num,
|
||||
measureOnly as num,
|
||||
no_changeturf as num,
|
||||
x_lower = -INFINITY as num,
|
||||
x_upper = INFINITY as num,
|
||||
y_lower = -INFINITY as num,
|
||||
y_upper = INFINITY as num,
|
||||
placeOnTop = FALSE as num,
|
||||
orientation = SOUTH as num,
|
||||
annihilate_tiles = FALSE,
|
||||
z_lower = -INFINITY as num,
|
||||
z_upper = INFINITY as num
|
||||
)
|
||||
var/datum/parsed_map/parsed = new(dmm_file, x_lower, x_upper, y_lower, y_upper, z_lower, z_upper, measureOnly)
|
||||
if(parsed.bounds && !measureOnly)
|
||||
parsed.load(x_offset, y_offset, z_offset, cropMap, no_changeturf, x_lower, x_upper, y_lower, y_upper, placeOnTop)
|
||||
parsed.load(x_offset, y_offset, z_offset, cropMap, no_changeturf, x_lower, x_upper, y_lower, y_upper, placeOnTop, orientation, annihilate_tiles)
|
||||
return parsed
|
||||
|
||||
/// Parse a map, possibly cropping it.
|
||||
/datum/parsed_map/New(tfile, x_lower = -INFINITY, x_upper = INFINITY, y_lower = -INFINITY, y_upper=INFINITY, measureOnly=FALSE)
|
||||
//WHY THE HECK DO WE EVEN SUPPORT NEGATIVE COORDINATES, ALL IT IS IS A WASTE OF TIME AND CPU!!!???
|
||||
//DO NOT USE THIS TO TRIM MAPS UNLESS STRICTLY NEEDED! IT IS EXTREMELY EXPENSIVE TO DO SO!
|
||||
/datum/parsed_map/New(tfile, x_lower = -INFINITY, x_upper = INFINITY, y_lower = -INFINITY, y_upper = INFINITY, z_lower = -INFINITY, z_upper = INFINITY, measureOnly = FALSE)
|
||||
if(isfile(tfile))
|
||||
original_path = "[tfile]"
|
||||
tfile = file2text(tfile)
|
||||
@@ -57,6 +80,9 @@
|
||||
return
|
||||
|
||||
bounds = parsed_bounds = list(1.#INF, 1.#INF, 1.#INF, -1.#INF, -1.#INF, -1.#INF)
|
||||
ASSERT(x_upper >= x_lower)
|
||||
ASSERT(y_upper >= y_lower)
|
||||
ASSERT(z_upper >= z_lower)
|
||||
var/stored_index = 1
|
||||
|
||||
//multiz lool
|
||||
@@ -82,20 +108,23 @@
|
||||
CRASH("Coords before model definition in DMM")
|
||||
|
||||
var/curr_x = text2num(dmmRegex.group[3])
|
||||
var/curr_y = text2num(dmmRegex.group[4])
|
||||
var/curr_z = text2num(dmmRegex.group[5])
|
||||
|
||||
if(curr_x < x_lower || curr_x > x_upper)
|
||||
if(curr_x < x_lower || curr_y < y_lower || curr_z < z_lower || curr_z > z_upper)
|
||||
continue
|
||||
|
||||
var/datum/grid_set/gridSet = new
|
||||
|
||||
gridSet.xcrd = curr_x
|
||||
//position of the currently processed square
|
||||
gridSet.ycrd = text2num(dmmRegex.group[4])
|
||||
gridSet.zcrd = text2num(dmmRegex.group[5])
|
||||
gridSet.ycrd = curr_y
|
||||
gridSet.zcrd = curr_z
|
||||
|
||||
bounds[MAP_MINX] = min(bounds[MAP_MINX], clamp(gridSet.xcrd, x_lower, x_upper))
|
||||
bounds[MAP_MINZ] = min(bounds[MAP_MINZ], gridSet.zcrd)
|
||||
bounds[MAP_MAXZ] = max(bounds[MAP_MAXZ], gridSet.zcrd)
|
||||
bounds[MAP_MINX] = min(bounds[MAP_MINX], curr_x) //since down is up for y/gridlines, we now know the lower left corner.
|
||||
bounds[MAP_MINY] = min(bounds[MAP_MINY], curr_y)
|
||||
bounds[MAP_MINZ] = min(bounds[MAP_MINZ], curr_z)
|
||||
|
||||
bounds[MAP_MAXZ] = max(bounds[MAP_MAXZ], curr_z) //we know max z now
|
||||
|
||||
var/list/gridLines = splittext(dmmRegex.group[6], "\n")
|
||||
gridSet.gridLines = gridLines
|
||||
@@ -105,102 +134,132 @@
|
||||
if(leadingBlanks > 1)
|
||||
gridLines.Cut(1, leadingBlanks) // Remove all leading blank lines.
|
||||
|
||||
if(!gridLines.len) // Skip it if only blank lines exist.
|
||||
continue
|
||||
|
||||
gridSets += gridSet
|
||||
|
||||
if(gridLines.len && gridLines[gridLines.len] == "")
|
||||
gridLines.Cut(gridLines.len) // Remove only one blank line at the end.
|
||||
var/lines = length(gridLines)
|
||||
if(lines)
|
||||
if(gridLines[gridLines.len] == "")
|
||||
gridLines.Cut(gridLines.len) // Remove only one blank line at the end.
|
||||
var/right_length = y_upper - curr_y + 1
|
||||
if(lines > right_length)
|
||||
gridLines.len = right_length //this can't be negative due to our ASSERTions above, hopefully.
|
||||
|
||||
bounds[MAP_MINY] = min(bounds[MAP_MINY], clamp(gridSet.ycrd, y_lower, y_upper))
|
||||
if(!gridLines.len) // Skip it if there's no content.
|
||||
continue
|
||||
|
||||
//do not use curr_y after this point, ycrd has changed. use it before because local var.
|
||||
gridSet.ycrd += gridLines.len - 1 // Start at the top and work down
|
||||
bounds[MAP_MAXY] = max(bounds[MAP_MAXY], clamp(gridSet.ycrd, y_lower, y_upper))
|
||||
bounds[MAP_MAXY] = max(bounds[MAP_MAXY], gridSet.ycrd) //we know max y now
|
||||
|
||||
var/maxx = gridSet.xcrd
|
||||
if(gridLines.len) //Not an empty map
|
||||
maxx = max(maxx, gridSet.xcrd + length(gridLines[1]) / key_len - 1)
|
||||
var/linelength = length(gridLines[1]) //yes it only samples the first line, this is why you use TGM instead of DMM!
|
||||
var/xlength = linelength / key_len
|
||||
|
||||
bounds[MAP_MAXX] = clamp(max(bounds[MAP_MAXX], maxx), x_lower, x_upper)
|
||||
var/maxx = gridSet.xcrd + xlength - 1
|
||||
if(maxx > x_upper)
|
||||
for(var/i in 1 to length(gridLines))
|
||||
gridLines[i] = copytext(gridLines[i], 1, key_len * (x_upper - curr_x + 1))
|
||||
bounds[MAP_MAXX] = max(bounds[MAP_MAXX], maxx)
|
||||
CHECK_TICK
|
||||
|
||||
// Indicate failure to parse any coordinates by nulling bounds
|
||||
if(bounds[1] == 1.#INF)
|
||||
bounds = null
|
||||
else
|
||||
width = bounds[MAP_MAXX] - bounds[MAP_MINX] + 1
|
||||
height = bounds[MAP_MAXY] - bounds[MAP_MINY] + 1
|
||||
parsed_bounds = bounds
|
||||
|
||||
/datum/parsed_map/Destroy()
|
||||
if(template_host && template_host.cached_map == src)
|
||||
template_host.cached_map = null
|
||||
return ..()
|
||||
|
||||
/// Load the parsed map into the world. See [/proc/load_map] for arguments.
|
||||
/datum/parsed_map/proc/load(x_offset, y_offset, z_offset, cropMap, no_changeturf, x_lower, x_upper, y_lower, y_upper, placeOnTop)
|
||||
/datum/parsed_map/proc/load(x_offset, y_offset, z_offset, cropMap, no_changeturf, x_lower, x_upper, y_lower, y_upper, placeOnTop, orientation, annihilate_tiles, datum/map_orientation_pattern/forced_pattern)
|
||||
//How I wish for RAII
|
||||
Master.StartLoadingMap()
|
||||
. = _load_impl(x_offset, y_offset, z_offset, cropMap, no_changeturf, x_lower, x_upper, y_lower, y_upper, placeOnTop)
|
||||
. = _load_impl(x_offset, y_offset, z_offset, cropMap, no_changeturf, x_lower, x_upper, y_lower, y_upper, placeOnTop, orientation, annihilate_tiles, forced_pattern)
|
||||
Master.StopLoadingMap()
|
||||
|
||||
// Do not call except via load() above.
|
||||
/datum/parsed_map/proc/_load_impl(x_offset = 1, y_offset = 1, z_offset = world.maxz + 1, cropMap = FALSE, no_changeturf = FALSE, x_lower = -INFINITY, x_upper = INFINITY, y_lower = -INFINITY, y_upper = INFINITY, placeOnTop = FALSE)
|
||||
// Lower/upper here refers to the actual map template's parsed coordinates, NOT ACTUAL COORDINATES! Figure it out yourself my head hurts too much to implement that too.
|
||||
/datum/parsed_map/proc/_load_impl(x_offset = 1, y_offset = 1, z_offset = world.maxz + 1, cropMap = FALSE, no_changeturf = FALSE, x_lower = -INFINITY, x_upper = INFINITY, y_lower = -INFINITY, y_upper = INFINITY, placeOnTop = FALSE, orientation = SOUTH, annihilate_tiles = FALSE, datum/map_orientation_pattern/forced_pattern)
|
||||
var/list/areaCache = list()
|
||||
var/list/modelCache = build_cache(no_changeturf)
|
||||
var/space_key = modelCache[SPACE_KEY]
|
||||
var/list/bounds
|
||||
src.bounds = bounds = list(1.#INF, 1.#INF, 1.#INF, -1.#INF, -1.#INF, -1.#INF)
|
||||
var/datum/map_orientation_pattern/mode = forced_pattern || GLOB.map_orientation_patterns["[orientation]"] || GLOB.map_orientation_patterns["[SOUTH]"]
|
||||
var/invert_y = mode.invert_y
|
||||
var/invert_x = mode.invert_x
|
||||
var/swap_xy = mode.swap_xy
|
||||
var/xi = mode.xi
|
||||
var/yi = mode.yi
|
||||
var/turn_angle = round(SIMPLIFY_DEGREES(mode.turn_angle), 90)
|
||||
var/delta_swap = x_offset - y_offset
|
||||
|
||||
for(var/I in gridSets)
|
||||
var/datum/grid_set/gset = I
|
||||
var/ycrd = gset.ycrd + y_offset - 1
|
||||
var/zcrd = gset.zcrd + z_offset - 1
|
||||
if(!cropMap && ycrd > world.maxy)
|
||||
world.maxy = ycrd // Expand Y here. X is expanded in the loop below
|
||||
var/zexpansion = zcrd > world.maxz
|
||||
for(var/__I in gridSets)
|
||||
var/datum/grid_set/gridset = __I
|
||||
var/parsed_z = gridset.zcrd + z_offset - 1
|
||||
var/zexpansion = parsed_z > world.maxz
|
||||
if(zexpansion)
|
||||
if(cropMap)
|
||||
continue
|
||||
else
|
||||
while (zcrd > world.maxz) //create a new z_level if needed
|
||||
while(parsed_z > world.maxz)
|
||||
world.incrementMaxZ()
|
||||
if(!no_changeturf)
|
||||
WARNING("Z-level expansion occurred without no_changeturf set, this may cause problems when /turf/AfterChange is called")
|
||||
//these values are the same until a new gridset is reached.
|
||||
var/edge_dist_x = gridset.xcrd - 1 //from left side, 0 is right on the x_offset
|
||||
var/edge_dist_y = gridset.ycrd - length(gridset.gridLines) //from bottom, 0 is right on the y_offset
|
||||
var/actual_x_starting = invert_x? (x_offset + width - edge_dist_x - 1) : (x_offset + edge_dist_x) //this value is not changed, cache.
|
||||
//this value is changed
|
||||
var/actual_y = invert_y? (y_offset + edge_dist_y) : (y_offset + gridset.ycrd - 1)
|
||||
for(var/line in gridset.gridLines)
|
||||
var/actual_x = actual_x_starting
|
||||
for(var/pos = 1 to (length(line) - key_len + 1) step key_len)
|
||||
var/placement_x = swap_xy? (actual_y + delta_swap) : actual_x
|
||||
var/placement_y = swap_xy? (actual_x - delta_swap) : actual_y
|
||||
if(placement_x > world.maxx)
|
||||
if(cropMap)
|
||||
actual_x += xi
|
||||
continue
|
||||
else
|
||||
world.maxx = placement_x
|
||||
if(placement_y > world.maxy)
|
||||
if(cropMap)
|
||||
break
|
||||
else
|
||||
world.maxy = placement_y
|
||||
if(placement_x < 1)
|
||||
actual_x += xi
|
||||
continue
|
||||
if(placement_y < 1)
|
||||
break
|
||||
var/model_key = copytext(line, pos, pos + key_len)
|
||||
var/no_afterchange = no_changeturf || zexpansion
|
||||
if(!no_afterchange || (model_key != space_key))
|
||||
var/list/cache = modelCache[model_key]
|
||||
if(!cache)
|
||||
CRASH("Undefined model key in DMM: [model_key]")
|
||||
build_coordinate(areaCache, cache, locate(placement_x, placement_y, parsed_z), no_afterchange, placeOnTop, turn_angle, annihilate_tiles, swap_xy, invert_y, invert_x)
|
||||
|
||||
for(var/line in gset.gridLines)
|
||||
if((ycrd - y_offset + 1) < y_lower || (ycrd - y_offset + 1) > y_upper) //Reverse operation and check if it is out of bounds of cropping.
|
||||
--ycrd
|
||||
continue
|
||||
if(ycrd <= world.maxy && ycrd >= 1)
|
||||
var/xcrd = gset.xcrd + x_offset - 1
|
||||
for(var/tpos = 1 to length(line) - key_len + 1 step key_len)
|
||||
if((xcrd - x_offset + 1) < x_lower || (xcrd - x_offset + 1) > x_upper) //Same as above.
|
||||
++xcrd
|
||||
continue //X cropping.
|
||||
if(xcrd > world.maxx)
|
||||
if(cropMap)
|
||||
break
|
||||
else
|
||||
world.maxx = xcrd
|
||||
|
||||
if(xcrd >= 1)
|
||||
var/model_key = copytext(line, tpos, tpos + key_len)
|
||||
var/no_afterchange = no_changeturf || zexpansion
|
||||
if(!no_afterchange || (model_key != space_key))
|
||||
var/list/cache = modelCache[model_key]
|
||||
if(!cache)
|
||||
CRASH("Undefined model key in DMM: [model_key]")
|
||||
build_coordinate(areaCache, cache, locate(xcrd, ycrd, zcrd), no_afterchange, placeOnTop)
|
||||
|
||||
// only bother with bounds that actually exist
|
||||
bounds[MAP_MINX] = min(bounds[MAP_MINX], xcrd)
|
||||
bounds[MAP_MINY] = min(bounds[MAP_MINY], ycrd)
|
||||
bounds[MAP_MINZ] = min(bounds[MAP_MINZ], zcrd)
|
||||
bounds[MAP_MAXX] = max(bounds[MAP_MAXX], xcrd)
|
||||
bounds[MAP_MAXY] = max(bounds[MAP_MAXY], ycrd)
|
||||
bounds[MAP_MAXZ] = max(bounds[MAP_MAXZ], zcrd)
|
||||
#ifdef TESTING
|
||||
else
|
||||
++turfsSkipped
|
||||
#endif
|
||||
CHECK_TICK
|
||||
++xcrd
|
||||
--ycrd
|
||||
|
||||
CHECK_TICK
|
||||
// only bother with bounds that actually exist
|
||||
bounds[MAP_MINX] = min(bounds[MAP_MINX], placement_x)
|
||||
bounds[MAP_MINY] = min(bounds[MAP_MINY], placement_y)
|
||||
bounds[MAP_MINZ] = min(bounds[MAP_MINZ], parsed_z)
|
||||
bounds[MAP_MAXX] = max(bounds[MAP_MAXX], placement_x)
|
||||
bounds[MAP_MAXY] = max(bounds[MAP_MAXY], placement_y)
|
||||
bounds[MAP_MAXZ] = max(bounds[MAP_MAXZ], parsed_z)
|
||||
#ifdef TESTING
|
||||
else
|
||||
++turfsSkipped
|
||||
#endif
|
||||
actual_x += xi
|
||||
CHECK_TICK
|
||||
actual_y += yi
|
||||
CHECK_TICK
|
||||
|
||||
if(!no_changeturf)
|
||||
for(var/t in block(locate(bounds[MAP_MINX], bounds[MAP_MINY], bounds[MAP_MINZ]), locate(bounds[MAP_MAXX], bounds[MAP_MAXY], bounds[MAP_MAXZ])))
|
||||
@@ -294,7 +353,7 @@
|
||||
|
||||
.[model_key] = list(members, members_attributes)
|
||||
|
||||
/datum/parsed_map/proc/build_coordinate(list/areaCache, list/model, turf/crds, no_changeturf as num, placeOnTop as num)
|
||||
/datum/parsed_map/proc/build_coordinate(list/areaCache, list/model, turf/crds, no_changeturf as num, placeOnTop as num, turn_angle as num, annihilate_tiles = FALSE, swap_xy, invert_y, invert_x)
|
||||
var/index
|
||||
var/list/members = model[1]
|
||||
var/list/members_attributes = model[2]
|
||||
@@ -306,6 +365,8 @@
|
||||
//The next part of the code assumes there's ALWAYS an /area AND a /turf on a given tile
|
||||
//first instance the /area and remove it from the members list
|
||||
index = members.len
|
||||
if(annihilate_tiles && crds)
|
||||
crds.empty(null)
|
||||
if(members[index] != /area/template_noop)
|
||||
var/atype = members[index]
|
||||
world.preloader_setup(members_attributes[index], atype)//preloader for assigning set variables on atom creation
|
||||
@@ -332,20 +393,20 @@
|
||||
//instanciate the first /turf
|
||||
var/turf/T
|
||||
if(members[first_turf_index] != /turf/template_noop)
|
||||
T = instance_atom(members[first_turf_index],members_attributes[first_turf_index],crds,no_changeturf,placeOnTop)
|
||||
T = instance_atom(members[first_turf_index],members_attributes[first_turf_index],crds,no_changeturf,placeOnTop,turn_angle, swap_xy, invert_y, invert_x)
|
||||
|
||||
if(T)
|
||||
//if others /turf are presents, simulates the underlays piling effect
|
||||
index = first_turf_index + 1
|
||||
while(index <= members.len - 1) // Last item is an /area
|
||||
var/underlay = T.appearance
|
||||
T = instance_atom(members[index],members_attributes[index],crds,no_changeturf,placeOnTop)//instance new turf
|
||||
T = instance_atom(members[index],members_attributes[index],crds,no_changeturf,placeOnTop,turn_angle, swap_xy, invert_y, invert_x)//instance new turf
|
||||
T.underlays += underlay
|
||||
index++
|
||||
|
||||
//finally instance all remainings objects/mobs
|
||||
for(index in 1 to first_turf_index-1)
|
||||
instance_atom(members[index],members_attributes[index],crds,no_changeturf,placeOnTop)
|
||||
instance_atom(members[index],members_attributes[index],crds,no_changeturf,placeOnTop,turn_angle, swap_xy, invert_y, invert_x)
|
||||
//Restore initialization to the previous value
|
||||
SSatoms.map_loader_stop()
|
||||
|
||||
@@ -354,8 +415,8 @@
|
||||
////////////////
|
||||
|
||||
//Instance an atom at (x,y,z) and gives it the variables in attributes
|
||||
/datum/parsed_map/proc/instance_atom(path,list/attributes, turf/crds, no_changeturf, placeOnTop)
|
||||
world.preloader_setup(attributes, path)
|
||||
/datum/parsed_map/proc/instance_atom(path,list/attributes, turf/crds, no_changeturf, placeOnTop, turn_angle = 0, swap_xy, invert_y, invert_x)
|
||||
world.preloader_setup(attributes, path, turn_angle, invert_x, invert_y, swap_xy)
|
||||
|
||||
if(crds)
|
||||
if(ispath(path, /turf))
|
||||
|
||||
@@ -11,9 +11,17 @@
|
||||
|
||||
for(var/turf/check in get_affected_turfs(central_turf,1))
|
||||
var/area/new_area = get_area(check)
|
||||
if(!(istype(new_area, allowed_areas)) || check.flags_1 & NO_RUINS_1)
|
||||
valid = FALSE
|
||||
valid = FALSE // set to false before we check
|
||||
if(check.flags_1 & NO_RUINS_1)
|
||||
break
|
||||
for(var/type in allowed_areas)
|
||||
if(istype(new_area, type)) // it's at least one of our types so it's whitelisted
|
||||
valid = TRUE
|
||||
break
|
||||
|
||||
if(!valid)
|
||||
break
|
||||
|
||||
|
||||
if(!valid)
|
||||
continue
|
||||
@@ -51,7 +59,7 @@
|
||||
new /obj/effect/landmark/ruin(center, src)
|
||||
return center
|
||||
|
||||
/proc/seedRuins(list/z_levels = null, budget = 0, whitelist = /area/space, list/potentialRuins)
|
||||
/proc/seedRuins(list/z_levels = null, budget = 0, whitelist = list(/area/space), list/potentialRuins)
|
||||
if(!z_levels || !z_levels.len)
|
||||
WARNING("No Z levels provided - Not generating ruins")
|
||||
return
|
||||
|
||||
@@ -659,6 +659,7 @@
|
||||
nemesis_factions = list("mining", "boss")
|
||||
var/transform_cooldown
|
||||
var/swiping = FALSE
|
||||
var/bleed_stacks_per_hit = 3
|
||||
total_mass = 2.75
|
||||
total_mass_on = 5
|
||||
|
||||
@@ -701,12 +702,11 @@
|
||||
user.changeNext_move(CLICK_CD_MELEE * 0.5) //when closed, it attacks very rapidly
|
||||
|
||||
/obj/item/melee/transforming/cleaving_saw/nemesis_effects(mob/living/user, mob/living/target)
|
||||
var/datum/status_effect/saw_bleed/B = target.has_status_effect(STATUS_EFFECT_SAWBLEED)
|
||||
var/datum/status_effect/stacking/saw_bleed/B = target.has_status_effect(STATUS_EFFECT_SAWBLEED)
|
||||
if(!B)
|
||||
if(!active) //This isn't in the above if-check so that the else doesn't care about active
|
||||
target.apply_status_effect(STATUS_EFFECT_SAWBLEED)
|
||||
target.apply_status_effect(STATUS_EFFECT_SAWBLEED,bleed_stacks_per_hit)
|
||||
else
|
||||
B.add_bleed(B.bleed_buildup)
|
||||
B.add_stacks(bleed_stacks_per_hit)
|
||||
|
||||
/obj/item/melee/transforming/cleaving_saw/attack(mob/living/target, mob/living/carbon/human/user)
|
||||
if(!active || swiping || !target.density || get_turf(target) == get_turf(user))
|
||||
|
||||
@@ -37,16 +37,19 @@
|
||||
/obj/machinery/mineral/ore_redemption/RefreshParts()
|
||||
var/ore_pickup_rate_temp = 15
|
||||
var/point_upgrade_temp = 1
|
||||
var/ore_multiplier_temp = 1
|
||||
var/avg_bin_level = 0
|
||||
var/bins = 0
|
||||
for(var/obj/item/stock_parts/matter_bin/B in component_parts)
|
||||
ore_multiplier_temp = 0.65 + (0.35 * B.rating)
|
||||
avg_bin_level = B.rating
|
||||
bins++
|
||||
for(var/obj/item/stock_parts/manipulator/M in component_parts)
|
||||
ore_pickup_rate_temp = 15 * M.rating
|
||||
for(var/obj/item/stock_parts/micro_laser/L in component_parts)
|
||||
point_upgrade_temp = 0.65 + (0.35 * L.rating)
|
||||
avg_bin_level /= bins? bins : 1
|
||||
ore_multiplier = STANDARD_PART_LEVEL_ORE_COEFFICIENT(avg_bin_level)
|
||||
ore_pickup_rate = ore_pickup_rate_temp
|
||||
point_upgrade = point_upgrade_temp
|
||||
ore_multiplier = round(ore_multiplier_temp, 0.01)
|
||||
|
||||
/obj/machinery/mineral/ore_redemption/examine(mob/user)
|
||||
. = ..()
|
||||
|
||||
@@ -18,8 +18,6 @@
|
||||
hair_color = random_short_color()
|
||||
facial_hair_color = hair_color
|
||||
eye_color = random_eye_color()
|
||||
horn_color = "85615a"
|
||||
wing_color = "fff"
|
||||
if(!pref_species)
|
||||
var/rando_race = pick(GLOB.roundstart_races)
|
||||
pref_species = new rando_race()
|
||||
|
||||
@@ -67,10 +67,8 @@
|
||||
//Special / holdover traits for Citadel specific sprites.
|
||||
var/extra = FALSE
|
||||
var/extra_color_src = MUTCOLORS2 //The color source for the extra overlay.
|
||||
var/extra_icon = 'modular_citadel/icons/mob/mam_tails.dmi'
|
||||
var/extra2 = FALSE
|
||||
var/extra2_color_src = MUTCOLORS3
|
||||
var/extra2_icon = 'modular_citadel/icons/mob/mam_tails.dmi'
|
||||
|
||||
//for snowflake/donor specific sprites
|
||||
var/list/ckeys_allowed
|
||||
|
||||
@@ -223,6 +223,8 @@
|
||||
icon_state = "cat"
|
||||
icon = 'icons/mob/mutant_bodyparts.dmi'
|
||||
color_src = HAIR
|
||||
extra = TRUE
|
||||
extra_color_src = NONE
|
||||
|
||||
/datum/sprite_accessory/mam_ears/catbig
|
||||
name = "Cat, Big"
|
||||
|
||||
@@ -21,8 +21,6 @@
|
||||
|
||||
/datum/sprite_accessory/taur
|
||||
icon = 'modular_citadel/icons/mob/mam_taur.dmi'
|
||||
extra_icon = 'modular_citadel/icons/mob/mam_taur.dmi'
|
||||
extra2_icon = 'modular_citadel/icons/mob/mam_taur.dmi'
|
||||
center = TRUE
|
||||
dimension_x = 64
|
||||
color_src = MATRIXED
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
/datum/sprite_accessory/snouts
|
||||
icon = 'icons/mob/mutant_bodyparts.dmi'
|
||||
mutant_part_string = "snout"
|
||||
relevant_layers = list(BODY_ADJ_LAYER, BODY_FRONT_LAYER)
|
||||
|
||||
/datum/sprite_accessory/snouts/sharp
|
||||
name = "Sharp"
|
||||
@@ -154,6 +155,7 @@
|
||||
icon = 'modular_citadel/icons/mob/mam_snouts.dmi'
|
||||
recommended_species = list("mammal", "slimeperson", "insect", "podweak")
|
||||
mutant_part_string = "snout"
|
||||
relevant_layers = list(BODY_ADJ_LAYER, BODY_FRONT_LAYER)
|
||||
|
||||
/datum/sprite_accessory/mam_snouts/none
|
||||
name = "None"
|
||||
@@ -386,4 +388,4 @@
|
||||
/datum/sprite_accessory/mam_snouts/froundlight
|
||||
name = "Round + Light (Top)"
|
||||
icon_state = "froundlight"
|
||||
color_src = MUTCOLORS
|
||||
color_src = MUTCOLORS
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
/datum/sprite_accessory/spines
|
||||
icon = 'icons/mob/mutant_bodyparts.dmi'
|
||||
relevant_layers = list(BODY_BEHIND_LAYER, BODY_ADJ_LAYER)
|
||||
|
||||
/datum/sprite_accessory/spines_animated
|
||||
icon = 'icons/mob/mutant_bodyparts.dmi'
|
||||
relevant_layers = list(BODY_BEHIND_LAYER, BODY_ADJ_LAYER)
|
||||
|
||||
/datum/sprite_accessory/spines/none
|
||||
name = "None"
|
||||
@@ -51,4 +53,4 @@
|
||||
|
||||
/datum/sprite_accessory/spines_animated/aqautic
|
||||
name = "Aquatic"
|
||||
icon_state = "aqua"
|
||||
icon_state = "aqua"
|
||||
|
||||
@@ -523,6 +523,7 @@
|
||||
color_src = MATRIXED
|
||||
icon = 'modular_citadel/icons/mob/mam_tails.dmi'
|
||||
mutant_part_string = "tailwag"
|
||||
relevant_layers = list(BODY_BEHIND_LAYER, BODY_FRONT_LAYER)
|
||||
|
||||
/datum/sprite_accessory/mam_tails_animated/none
|
||||
name = "None"
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
|
||||
/datum/sprite_accessory/wings_open
|
||||
icon = 'icons/mob/wings.dmi'
|
||||
relevant_layers = list(BODY_BEHIND_LAYER, BODY_ADJ_LAYER, BODY_FRONT_LAYER)
|
||||
|
||||
/datum/sprite_accessory/wings_open/angel
|
||||
name = "Angel"
|
||||
@@ -49,6 +50,7 @@
|
||||
dimension_x = 46
|
||||
center = TRUE
|
||||
dimension_y = 34
|
||||
relevant_layers = list(BODY_BEHIND_LAYER, BODY_ADJ_LAYER, BODY_FRONT_LAYER)
|
||||
|
||||
/datum/sprite_accessory/deco_wings/bat
|
||||
name = "Bat"
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
status_flags = CANSTUN|CANKNOCKDOWN|CANUNCONSCIOUS|CANPUSH|CANSTAGGER
|
||||
|
||||
blocks_emissive = EMISSIVE_BLOCK_UNIQUE
|
||||
|
||||
|
||||
//Hair colour and style
|
||||
var/hair_color = "000"
|
||||
var/hair_style = "Bald"
|
||||
@@ -23,10 +23,6 @@
|
||||
//Eye colour
|
||||
var/eye_color = "000"
|
||||
|
||||
var/horn_color = "85615a" //specific horn colors, because why not?
|
||||
|
||||
var/wing_color = "fff" //wings too
|
||||
|
||||
var/skin_tone = "caucasian1" //Skin tone
|
||||
|
||||
var/lip_style = null //no lipstick by default- arguably misleading, as it could be used for general makeup
|
||||
|
||||
@@ -24,13 +24,18 @@
|
||||
/mob/living/carbon/human/slip(knockdown_amount, obj/O, lube)
|
||||
if(HAS_TRAIT(src, TRAIT_NOSLIPALL))
|
||||
return 0
|
||||
if (!(lube&GALOSHES_DONT_HELP))
|
||||
if (!(lube & GALOSHES_DONT_HELP))
|
||||
if(HAS_TRAIT(src, TRAIT_NOSLIPWATER))
|
||||
return 0
|
||||
if(shoes && istype(shoes, /obj/item/clothing))
|
||||
var/obj/item/clothing/CS = shoes
|
||||
if (CS.clothing_flags & NOSLIP)
|
||||
return 0
|
||||
if (lube & SLIDE_ICE)
|
||||
if(shoes && istype(shoes, /obj/item/clothing))
|
||||
var/obj/item/clothing/CS = shoes
|
||||
if (CS.clothing_flags & NOSLIP_ICE)
|
||||
return FALSE
|
||||
return ..()
|
||||
|
||||
/mob/living/carbon/human/experience_pressure_difference()
|
||||
|
||||
@@ -35,10 +35,6 @@ GLOBAL_LIST_EMPTY(roundstart_race_names)
|
||||
|
||||
var/hair_color // this allows races to have specific hair colors... if null, it uses the H's hair/facial hair colors. if "mutcolor", it uses the H's mutant_color
|
||||
var/hair_alpha = 255 // the alpha used by the hair. 255 is completely solid, 0 is transparent.
|
||||
|
||||
var/horn_color //specific horn colors, because why not?
|
||||
var/wing_color
|
||||
|
||||
var/use_skintones = NO_SKINTONES // does it use skintones or not? (spoiler alert this is only used by humans)
|
||||
var/exotic_blood = "" // If your race wants to bleed something other than bog standard blood, change this to reagent id.
|
||||
var/exotic_bloodtype = "" //If your race uses a non standard bloodtype (A+, O-, AB-, etc)
|
||||
@@ -882,9 +878,9 @@ GLOBAL_LIST_EMPTY(roundstart_race_names)
|
||||
if(EYECOLOR)
|
||||
accessory_overlay.color = "#[H.eye_color]"
|
||||
if(HORNCOLOR)
|
||||
accessory_overlay.color = "#[H.horn_color]"
|
||||
accessory_overlay.color = "#[H.dna.features["horns_color"]]"
|
||||
if(WINGCOLOR)
|
||||
accessory_overlay.color = "#[H.wing_color]"
|
||||
accessory_overlay.color = "#[H.dna.features["wings_color"]]"
|
||||
else
|
||||
accessory_overlay.color = forced_colour
|
||||
else
|
||||
@@ -938,9 +934,9 @@ GLOBAL_LIST_EMPTY(roundstart_race_names)
|
||||
extra_accessory_overlay.color = "#[H.eye_color]"
|
||||
|
||||
if(HORNCOLOR)
|
||||
extra_accessory_overlay.color = "#[H.horn_color]"
|
||||
extra_accessory_overlay.color = "#[H.dna.features["horns_color"]]"
|
||||
if(WINGCOLOR)
|
||||
extra_accessory_overlay.color = "#[H.wing_color]"
|
||||
extra_accessory_overlay.color = "#[H.dna.features["wings_color"]]"
|
||||
|
||||
if(OFFSET_MUTPARTS in H.dna.species.offset_features)
|
||||
extra_accessory_overlay.pixel_x += H.dna.species.offset_features[OFFSET_MUTPARTS][1]
|
||||
@@ -979,9 +975,9 @@ GLOBAL_LIST_EMPTY(roundstart_race_names)
|
||||
else
|
||||
extra2_accessory_overlay.color = "#[H.hair_color]"
|
||||
if(HORNCOLOR)
|
||||
extra2_accessory_overlay.color = "#[H.horn_color]"
|
||||
extra2_accessory_overlay.color = "#[H.dna.features["horns_color"]]"
|
||||
if(WINGCOLOR)
|
||||
extra2_accessory_overlay.color = "#[H.wing_color]"
|
||||
extra2_accessory_overlay.color = "#[H.dna.features["wings_color"]]"
|
||||
|
||||
if(OFFSET_MUTPARTS in H.dna.species.offset_features)
|
||||
extra2_accessory_overlay.pixel_x += H.dna.species.offset_features[OFFSET_MUTPARTS][1]
|
||||
|
||||
@@ -316,6 +316,45 @@
|
||||
|
||||
update_pull_movespeed()
|
||||
|
||||
set_pull_offsets(M, state)
|
||||
|
||||
/mob/living/proc/set_pull_offsets(mob/living/M, grab_state = GRAB_PASSIVE)
|
||||
if(M.buckled || M.combat_flags & COMBAT_FLAG_COMBAT_ACTIVE)
|
||||
return //don't make them change direction or offset them if they're buckled into something or in combat mode.
|
||||
var/offset = 0
|
||||
switch(grab_state)
|
||||
if(GRAB_PASSIVE)
|
||||
offset = GRAB_PIXEL_SHIFT_PASSIVE
|
||||
if(GRAB_AGGRESSIVE)
|
||||
offset = GRAB_PIXEL_SHIFT_AGGRESSIVE
|
||||
if(GRAB_NECK)
|
||||
offset = GRAB_PIXEL_SHIFT_NECK
|
||||
if(GRAB_KILL)
|
||||
offset = GRAB_PIXEL_SHIFT_NECK
|
||||
M.setDir(get_dir(M, src))
|
||||
switch(M.dir)
|
||||
if(NORTH)
|
||||
animate(M, pixel_x = 0, pixel_y = offset, 3)
|
||||
if(SOUTH)
|
||||
animate(M, pixel_x = 0, pixel_y = -offset, 3)
|
||||
if(EAST)
|
||||
if(M.lying == 270) //update the dragged dude's direction if we've turned
|
||||
M.lying = 90
|
||||
M.update_transform() //force a transformation update, otherwise it'll take a few ticks for update_mobility() to do so
|
||||
M.lying_prev = M.lying
|
||||
animate(M, pixel_x = offset, pixel_y = 0, 3)
|
||||
if(WEST)
|
||||
if(M.lying == 90)
|
||||
M.lying = 270
|
||||
M.update_transform()
|
||||
M.lying_prev = M.lying
|
||||
animate(M, pixel_x = -offset, pixel_y = 0, 3)
|
||||
|
||||
/mob/living/proc/reset_pull_offsets(mob/living/M, override)
|
||||
if(!override && M.buckled)
|
||||
return
|
||||
animate(M, pixel_x = 0, pixel_y = 0, 1)
|
||||
|
||||
//mob verbs are a lot faster than object verbs
|
||||
//for more info on why this is not atom/pull, see examinate() in mob.dm
|
||||
/mob/living/verb/pulled(atom/movable/AM as mob|obj in oview(1))
|
||||
@@ -328,6 +367,8 @@
|
||||
stop_pulling()
|
||||
|
||||
/mob/living/stop_pulling()
|
||||
if(ismob(pulling))
|
||||
reset_pull_offsets(pulling)
|
||||
..()
|
||||
update_pull_movespeed()
|
||||
update_pull_hud_icon()
|
||||
|
||||
@@ -264,6 +264,7 @@
|
||||
update_mobility() //we fall down
|
||||
if(!buckled && !density)
|
||||
Move(user.loc)
|
||||
user.set_pull_offsets(src, grab_state)
|
||||
return 1
|
||||
|
||||
/mob/living/attack_hand(mob/user)
|
||||
|
||||
@@ -75,8 +75,12 @@
|
||||
|
||||
. = ..()
|
||||
|
||||
if(pulledby && moving_diagonally != FIRST_DIAG_STEP && get_dist(src, pulledby) > 1)//separated from our puller and not in the middle of a diagonal move.
|
||||
if(pulledby && moving_diagonally != FIRST_DIAG_STEP && get_dist(src, pulledby) > 1 && (pulledby != moving_from_pull))//separated from our puller and not in the middle of a diagonal move.
|
||||
pulledby.stop_pulling()
|
||||
else
|
||||
if(isliving(pulledby))
|
||||
var/mob/living/L = pulledby
|
||||
L.set_pull_offsets(src, pulledby.grab_state)
|
||||
|
||||
if(active_storage && !(CanReach(active_storage.parent,view_only = TRUE)))
|
||||
active_storage.close(src)
|
||||
@@ -84,6 +88,14 @@
|
||||
if(lying && !buckled && prob(getBruteLoss()*200/maxHealth))
|
||||
makeTrail(newloc, T, old_direction)
|
||||
|
||||
|
||||
/mob/living/Move_Pulled(atom/A)
|
||||
. = ..()
|
||||
if(!. || !isliving(A))
|
||||
return
|
||||
var/mob/living/L = A
|
||||
set_pull_offsets(L, grab_state)
|
||||
|
||||
/mob/living/forceMove(atom/destination)
|
||||
stop_pulling()
|
||||
if(buckled)
|
||||
|
||||
@@ -265,4 +265,12 @@ Difficulty: Medium
|
||||
desc = "The sweet blood, oh, it sings to me."
|
||||
invisibility = 100
|
||||
|
||||
/mob/living/simple_animal/hostile/megafauna/blood_drunk_miner/doom
|
||||
name = "hostile-environment miner"
|
||||
desc = "A miner destined to hop across dimensions for all eternity, hunting anomalous creatures."
|
||||
speed = 8
|
||||
move_to_delay = 8
|
||||
ranged_cooldown_time = 8
|
||||
dash_cooldown = 8
|
||||
|
||||
#undef MINER_DASH_RANGE
|
||||
|
||||
@@ -0,0 +1,361 @@
|
||||
/*
|
||||
Difficulty: Extremely Hard
|
||||
*/
|
||||
|
||||
/mob/living/simple_animal/hostile/megafauna/demonic_frost_miner
|
||||
name = "demonic-frost miner"
|
||||
desc = "An extremely geared miner, driven crazy or possessed by the demonic forces here, either way a terrifying enemy."
|
||||
health = 1500
|
||||
maxHealth = 1500
|
||||
icon_state = "demonic_miner"
|
||||
icon_living = "demonic_miner"
|
||||
icon = 'icons/mob/icemoon/icemoon_monsters.dmi'
|
||||
attacktext = "pummels"
|
||||
attack_sound = 'sound/weapons/sonic_jackhammer.ogg'
|
||||
mob_biotypes = MOB_ORGANIC|MOB_HUMANOID
|
||||
light_color = "#E4C7C5"
|
||||
movement_type = GROUND
|
||||
weather_immunities = list("snow")
|
||||
speak_emote = list("roars")
|
||||
armour_penetration = 100
|
||||
melee_damage_lower = 10
|
||||
melee_damage_upper = 10
|
||||
aggro_vision_range = 18 // large vision range so combat doesn't abruptly end when someone runs a bit away
|
||||
rapid_melee = 4
|
||||
speed = 20
|
||||
move_to_delay = 20
|
||||
ranged = TRUE
|
||||
crusher_loot = list(/obj/effect/decal/remains/plasma, /obj/item/crusher_trophy/ice_block_talisman)
|
||||
loot = list(/obj/effect/decal/remains/plasma)
|
||||
wander = FALSE
|
||||
del_on_death = TRUE
|
||||
blood_volume = BLOOD_VOLUME_NORMAL
|
||||
deathmessage = "falls to the ground, decaying into plasma particles."
|
||||
deathsound = "bodyfall"
|
||||
attack_action_types = list(/datum/action/innate/megafauna_attack/frost_orbs,
|
||||
/datum/action/innate/megafauna_attack/snowball_machine_gun,
|
||||
/datum/action/innate/megafauna_attack/ice_shotgun)
|
||||
/// Modifies the speed of the projectiles the demonic frost miner shoots out
|
||||
var/projectile_speed_multiplier = 1
|
||||
/// If the demonic frost miner is in its enraged state
|
||||
var/enraged = FALSE
|
||||
/// If the demonic frost miner is currently transforming to its enraged state
|
||||
var/enraging = FALSE
|
||||
|
||||
/mob/living/simple_animal/hostile/megafauna/demonic_frost_miner/Initialize()
|
||||
. = ..()
|
||||
AddComponent(/datum/component/knockback, 7, FALSE, TRUE)
|
||||
AddComponent(/datum/component/lifesteal, 50)
|
||||
|
||||
/datum/action/innate/megafauna_attack/frost_orbs
|
||||
name = "Fire Frost Orbs"
|
||||
icon_icon = 'icons/mob/actions/actions_items.dmi'
|
||||
button_icon_state = "sniper_zoom"
|
||||
chosen_message = "<span class='colossus'>You are now sending out frost orbs to track in on a target.</span>"
|
||||
chosen_attack_num = 1
|
||||
|
||||
/datum/action/innate/megafauna_attack/snowball_machine_gun
|
||||
name = "Fire Snowball Machine Gun"
|
||||
icon_icon = 'icons/obj/guns/energy.dmi'
|
||||
button_icon_state = "kineticgun"
|
||||
chosen_message = "<span class='colossus'>You are now firing a snowball machine gun at a target.</span>"
|
||||
chosen_attack_num = 2
|
||||
|
||||
/datum/action/innate/megafauna_attack/ice_shotgun
|
||||
name = "Fire Ice Shotgun"
|
||||
icon_icon = 'icons/obj/guns/projectile.dmi'
|
||||
button_icon_state = "shotgun"
|
||||
chosen_message = "<span class='colossus'>You are now firing shotgun ice blasts.</span>"
|
||||
chosen_attack_num = 3
|
||||
|
||||
/mob/living/simple_animal/hostile/megafauna/demonic_frost_miner/OpenFire()
|
||||
check_enraged()
|
||||
projectile_speed_multiplier = 1 - enraged * 0.25
|
||||
SetRecoveryTime(100, 100)
|
||||
|
||||
if(client)
|
||||
switch(chosen_attack)
|
||||
if(1)
|
||||
frost_orbs()
|
||||
if(2)
|
||||
snowball_machine_gun()
|
||||
if(3)
|
||||
ice_shotgun()
|
||||
return
|
||||
|
||||
var/easy_attack = prob(80 - enraged * 40)
|
||||
chosen_attack = rand(1, 3)
|
||||
switch(chosen_attack)
|
||||
if(1)
|
||||
if(easy_attack)
|
||||
frost_orbs(10, 8)
|
||||
else
|
||||
frost_orbs(5, 16)
|
||||
if(2)
|
||||
if(easy_attack)
|
||||
snowball_machine_gun()
|
||||
else
|
||||
INVOKE_ASYNC(src, .proc/ice_shotgun, 5, list(list(-180, -140, -100, -60, -20, 20, 60, 100, 140), list(-160, -120, -80, -40, 0, 40, 80, 120, 160)))
|
||||
snowball_machine_gun(5 * 8, 5)
|
||||
if(3)
|
||||
if(easy_attack)
|
||||
ice_shotgun()
|
||||
else
|
||||
ice_shotgun(5, list(list(0, 30, 60, 90, 120, 150, 180, 210, 240, 270, 300, 330), list(-30, -15, 0, 15, 30)))
|
||||
|
||||
/obj/item/projectile/frost_orb
|
||||
name = "frost orb"
|
||||
icon_state = "ice_1"
|
||||
damage = 20
|
||||
armour_penetration = 100
|
||||
speed = 10
|
||||
homing_turn_speed = 30
|
||||
damage_type = BURN
|
||||
|
||||
/obj/item/projectile/frost_orb/on_hit(atom/target, blocked = FALSE)
|
||||
. = ..()
|
||||
if(isturf(target) || isobj(target))
|
||||
target.ex_act(EXPLODE_HEAVY)
|
||||
|
||||
/obj/item/projectile/snowball
|
||||
name = "machine-gun snowball"
|
||||
icon_state = "nuclear_particle"
|
||||
damage = 5
|
||||
armour_penetration = 100
|
||||
speed = 4
|
||||
damage_type = BRUTE
|
||||
|
||||
/obj/item/projectile/ice_blast
|
||||
name = "ice blast"
|
||||
icon_state = "ice_2"
|
||||
damage = 15
|
||||
armour_penetration = 100
|
||||
speed = 4
|
||||
damage_type = BRUTE
|
||||
|
||||
/obj/item/projectile/ice_blast/on_hit(atom/target, blocked = FALSE)
|
||||
. = ..()
|
||||
if(isturf(target) || isobj(target))
|
||||
target.ex_act(EXPLODE_HEAVY)
|
||||
|
||||
/mob/living/simple_animal/hostile/megafauna/demonic_frost_miner/ex_act(severity, target)
|
||||
adjustBruteLoss(30 * severity - 120)
|
||||
visible_message("<span class='danger'>[src] absorbs the explosion!</span>", "<span class='userdanger'>You absorb the explosion!</span>")
|
||||
|
||||
/mob/living/simple_animal/hostile/megafauna/demonic_frost_miner/Goto(target, delay, minimum_distance)
|
||||
if(enraging)
|
||||
return
|
||||
return ..()
|
||||
|
||||
/mob/living/simple_animal/hostile/megafauna/demonic_frost_miner/MoveToTarget(list/possible_targets)
|
||||
if(enraging)
|
||||
return
|
||||
return ..()
|
||||
|
||||
/mob/living/simple_animal/hostile/megafauna/demonic_frost_miner/Move()
|
||||
if(enraging)
|
||||
return
|
||||
return ..()
|
||||
|
||||
/// Shoots out homing frost orbs that explode into ice blast projectiles after a couple seconds
|
||||
/mob/living/simple_animal/hostile/megafauna/demonic_frost_miner/proc/frost_orbs(added_delay = 10, shoot_times = 8)
|
||||
for(var/i in 1 to shoot_times)
|
||||
var/turf/startloc = get_turf(src)
|
||||
var/turf/endloc = get_turf(target)
|
||||
if(!endloc)
|
||||
break
|
||||
var/obj/item/projectile/frost_orb/P = new(startloc)
|
||||
P.preparePixelProjectile(endloc, startloc)
|
||||
P.firer = src
|
||||
if(target)
|
||||
P.original = target
|
||||
P.set_homing_target(target)
|
||||
P.fire(rand(0, 360))
|
||||
addtimer(CALLBACK(P, /obj/item/projectile/frost_orb/proc/orb_explosion, projectile_speed_multiplier), 20) // make the orbs home in after a second
|
||||
SLEEP_CHECK_DEATH(added_delay)
|
||||
SetRecoveryTime(40, 60)
|
||||
|
||||
/// Called when the orb is exploding, shoots out projectiles
|
||||
/obj/item/projectile/frost_orb/proc/orb_explosion(projectile_speed_multiplier)
|
||||
for(var/i in 0 to 5)
|
||||
var/angle = i * 60
|
||||
var/turf/startloc = get_turf(src)
|
||||
var/turf/endloc = get_turf(original)
|
||||
if(!startloc || !endloc)
|
||||
break
|
||||
var/obj/item/projectile/ice_blast/P = new(startloc)
|
||||
P.speed *= projectile_speed_multiplier
|
||||
P.preparePixelProjectile(endloc, startloc, null, angle + rand(-10, 10))
|
||||
P.firer = firer
|
||||
if(original)
|
||||
P.original = original
|
||||
P.fire()
|
||||
qdel(src)
|
||||
|
||||
/// Shoots out snowballs with a random spread
|
||||
/mob/living/simple_animal/hostile/megafauna/demonic_frost_miner/proc/snowball_machine_gun(shots = 60, spread = 45)
|
||||
for(var/i in 1 to shots)
|
||||
var/turf/startloc = get_turf(src)
|
||||
var/turf/endloc = get_turf(target)
|
||||
if(!endloc)
|
||||
break
|
||||
var/obj/item/projectile/P = new /obj/item/projectile/snowball(startloc)
|
||||
P.speed *= projectile_speed_multiplier
|
||||
P.preparePixelProjectile(endloc, startloc, null, rand(-spread, spread))
|
||||
P.firer = src
|
||||
if(target)
|
||||
P.original = target
|
||||
P.fire()
|
||||
SLEEP_CHECK_DEATH(1)
|
||||
SetRecoveryTime(15, 15)
|
||||
|
||||
/// Shoots out ice blasts in a shotgun like pattern
|
||||
/mob/living/simple_animal/hostile/megafauna/demonic_frost_miner/proc/ice_shotgun(shots = 5, list/patterns = list(list(-40, -20, 0, 20, 40), list(-30, -10, 10, 30)))
|
||||
for(var/i in 1 to shots)
|
||||
var/list/pattern = patterns[i % length(patterns) + 1] // alternating patterns
|
||||
for(var/spread in pattern)
|
||||
var/turf/startloc = get_turf(src)
|
||||
var/turf/endloc = get_turf(target)
|
||||
if(!endloc)
|
||||
break
|
||||
var/obj/item/projectile/P = new /obj/item/projectile/ice_blast(startloc)
|
||||
P.speed *= projectile_speed_multiplier
|
||||
P.preparePixelProjectile(endloc, startloc, null, spread)
|
||||
P.firer = src
|
||||
if(target)
|
||||
P.original = target
|
||||
P.fire()
|
||||
SLEEP_CHECK_DEATH(8)
|
||||
SetRecoveryTime(15, 20)
|
||||
|
||||
/// Checks if the demonic frost miner is ready to be enraged
|
||||
/mob/living/simple_animal/hostile/megafauna/demonic_frost_miner/proc/check_enraged()
|
||||
if(enraged)
|
||||
return
|
||||
if(health > maxHealth*0.25)
|
||||
return
|
||||
SetRecoveryTime(80, 80)
|
||||
adjustHealth(-maxHealth)
|
||||
enraged = TRUE
|
||||
enraging = TRUE
|
||||
animate(src, pixel_y = pixel_y + 96, time = 100, easing = ELASTIC_EASING)
|
||||
spin(100, 10)
|
||||
SLEEP_CHECK_DEATH(60)
|
||||
playsound(src, 'sound/effects/explosion3.ogg', 100, TRUE)
|
||||
icon_state = "demonic_miner_phase2"
|
||||
animate(src, pixel_y = pixel_y - 96, time = 8, flags = ANIMATION_END_NOW)
|
||||
spin(8, 2)
|
||||
SLEEP_CHECK_DEATH(8)
|
||||
for(var/mob/living/L in viewers(src))
|
||||
shake_camera(L, 3, 2)
|
||||
playsound(src, 'sound/effects/meteorimpact.ogg', 100, TRUE)
|
||||
setMovetype(movement_type | FLYING)
|
||||
enraging = FALSE
|
||||
adjustHealth(-maxHealth)
|
||||
|
||||
/mob/living/simple_animal/hostile/megafauna/demonic_frost_miner/death(gibbed, list/force_grant)
|
||||
if(health > 0)
|
||||
return
|
||||
var/turf/T = get_turf(src)
|
||||
var/loot = rand(1, 3)
|
||||
switch(loot)
|
||||
if(1)
|
||||
new /obj/item/resurrection_crystal(T)
|
||||
if(2)
|
||||
new /obj/item/clothing/shoes/winterboots/ice_boots/speedy(T)
|
||||
if(3)
|
||||
new /obj/item/pickaxe/drill/jackhammer/demonic(T)
|
||||
return ..()
|
||||
|
||||
/obj/item/resurrection_crystal
|
||||
name = "resurrection crystal"
|
||||
desc = "When used by anything holding it, this crystal gives them a second chance at life if they die."
|
||||
icon = 'icons/obj/objects.dmi'
|
||||
icon_state = "demonic_crystal"
|
||||
|
||||
/obj/item/resurrection_crystal/attack_self(mob/living/user)
|
||||
if(!iscarbon(user))
|
||||
to_chat(user, "<span class='notice'>A dark presence stops you from absorbing the crystal.</span>")
|
||||
return
|
||||
forceMove(user)
|
||||
to_chat(user, "<span class='notice'>You feel a bit safer... but a demonic presence lurks in the back of your head...</span>")
|
||||
RegisterSignal(user, COMSIG_MOB_DEATH, .proc/resurrect)
|
||||
|
||||
/// Resurrects the target when they die by cloning them into a new duplicate body and transferring their mind to the clone on a safe station turf
|
||||
/obj/item/resurrection_crystal/proc/resurrect(mob/living/carbon/user, gibbed)
|
||||
user.visible_message("<span class='notice'>You see [user]'s soul dragged out of their body!</span>", "<span class='notice'>You feel your soul dragged away to a fresh body!</span>")
|
||||
var/typepath = user.type
|
||||
var/turf/T = find_safe_turf()
|
||||
var/mob/living/carbon/clone = new typepath(T)
|
||||
clone.real_name = user.real_name
|
||||
user.dna.transfer_identity(clone)
|
||||
clone.updateappearance(mutcolor_update=1)
|
||||
user.mind.transfer_to(clone) // second life
|
||||
to_chat(clone, "<span class='notice'>You blink and find yourself in [get_area_name(T)].</span>")
|
||||
user.gib()
|
||||
qdel(src)
|
||||
|
||||
/obj/item/clothing/shoes/winterboots/ice_boots/speedy
|
||||
name = "cursed ice hiking boots"
|
||||
desc = "A pair of winter boots contractually made by a devil, they cannot be taken off once put on."
|
||||
slowdown = SHOES_SPEED_SLIGHT
|
||||
|
||||
/obj/item/clothing/shoes/winterboots/ice_boots/speedy/Initialize()
|
||||
. = ..()
|
||||
ADD_TRAIT(src, TRAIT_NODROP, CURSED_ITEM_TRAIT)
|
||||
|
||||
/obj/item/pickaxe/drill/jackhammer/demonic
|
||||
name = "demonic jackhammer"
|
||||
desc = "Cracks rocks at an inhuman speed, as well as being enhanced for combat purposes."
|
||||
toolspeed = 0
|
||||
|
||||
/obj/item/pickaxe/drill/jackhammer/demonic/Initialize()
|
||||
. = ..()
|
||||
AddComponent(/datum/component/knockback, 4, FALSE, TRUE)
|
||||
AddComponent(/datum/component/lifesteal, 5)
|
||||
|
||||
/obj/item/crusher_trophy/ice_block_talisman
|
||||
name = "ice block talisman"
|
||||
desc = "A glowing trinket that a demonic miner had on him, it seems he couldn't utilize it for whatever reason."
|
||||
icon_state = "ice_trap_talisman"
|
||||
denied_type = /obj/item/crusher_trophy/ice_block_talisman
|
||||
|
||||
/obj/item/crusher_trophy/ice_block_talisman/effect_desc()
|
||||
return "mark detonation to freeze a creature in a block of ice for a period, preventing them from moving"
|
||||
|
||||
/obj/item/crusher_trophy/ice_block_talisman/on_mark_detonation(mob/living/target, mob/living/user)
|
||||
target.apply_status_effect(/datum/status_effect/ice_block_talisman)
|
||||
|
||||
/datum/status_effect/ice_block_talisman
|
||||
id = "ice_block_talisman"
|
||||
duration = 25
|
||||
status_type = STATUS_EFFECT_REFRESH
|
||||
alert_type = /obj/screen/alert/status_effect/ice_block_talisman
|
||||
/// Stored icon overlay for the hit mob, removed when effect is removed
|
||||
var/icon/cube
|
||||
|
||||
/obj/screen/alert/status_effect/ice_block_talisman
|
||||
name = "Frozen Solid"
|
||||
desc = "You're frozen inside an ice cube, and cannot move!"
|
||||
icon_state = "frozen"
|
||||
|
||||
/datum/status_effect/ice_block_talisman/on_apply()
|
||||
RegisterSignal(owner, COMSIG_MOVABLE_PRE_MOVE, .proc/owner_moved)
|
||||
if(!owner.stat)
|
||||
to_chat(owner, "<span class='userdanger'>You become frozen in a cube!</span>")
|
||||
cube = icon('icons/effects/freeze.dmi', "ice_cube")
|
||||
var/icon/size_check = icon(owner.icon, owner.icon_state)
|
||||
cube.Scale(size_check.Width(), size_check.Height())
|
||||
owner.add_overlay(cube)
|
||||
return ..()
|
||||
|
||||
/// Blocks movement from the status effect owner
|
||||
/datum/status_effect/ice_block_talisman/proc/owner_moved()
|
||||
return COMPONENT_MOVABLE_BLOCK_PRE_MOVE
|
||||
|
||||
/datum/status_effect/ice_block_talisman/on_remove()
|
||||
. = ..()
|
||||
if(!owner.stat)
|
||||
to_chat(owner, "<span class='notice'>The cube melts!</span>")
|
||||
owner.cut_overlay(cube)
|
||||
UnregisterSignal(owner, COMSIG_MOVABLE_PRE_MOVE)
|
||||
@@ -394,3 +394,26 @@ Difficulty: Medium
|
||||
|
||||
/mob/living/simple_animal/hostile/megafauna/dragon/lesser/grant_achievement(medaltype,scoretype)
|
||||
return
|
||||
|
||||
//fire line keeps going even if dragon is deleted
|
||||
/proc/dragon_fire_line(source, list/turfs)
|
||||
var/list/hit_list = list()
|
||||
for(var/turf/T in turfs)
|
||||
if(istype(T, /turf/closed))
|
||||
break
|
||||
new /obj/effect/hotspot(T)
|
||||
T.hotspot_expose(700,50,1)
|
||||
for(var/mob/living/L in T.contents)
|
||||
if(L in hit_list || L == source)
|
||||
continue
|
||||
hit_list += L
|
||||
L.adjustFireLoss(20)
|
||||
to_chat(L, "<span class='userdanger'>You're hit by [source]'s fire breath!</span>")
|
||||
|
||||
// deals damage to mechs
|
||||
for(var/obj/mecha/M in T.contents)
|
||||
if(M in hit_list)
|
||||
continue
|
||||
hit_list += M
|
||||
M.take_damage(45, BRUTE, "melee", 1)
|
||||
sleep(1.5)
|
||||
|
||||
@@ -27,23 +27,51 @@
|
||||
mob_size = MOB_SIZE_LARGE
|
||||
layer = LARGE_MOB_LAYER //Looks weird with them slipping under mineral walls and cameras and shit otherwise
|
||||
flags_1 = PREVENT_CONTENTS_EXPLOSION_1 | HEAR_1
|
||||
/// Crusher loot dropped when fauna killed with a crusher
|
||||
var/list/crusher_loot
|
||||
var/medal_type
|
||||
/// Score given to players when the fauna is killed
|
||||
var/score_type = BOSS_SCORE
|
||||
/// If the megafauna is actually killed (vs entering another phase)
|
||||
var/elimination = 0
|
||||
/// Modifies attacks when at lower health
|
||||
var/anger_modifier = 0
|
||||
/// Internal tracking GPS inside fauna
|
||||
var/obj/item/gps/internal
|
||||
/// Next time fauna can use a melee attack
|
||||
var/recovery_time = 0
|
||||
|
||||
var/true_spawn = TRUE // if this is a megafauna that should grant achievements, or have a gps signal
|
||||
var/nest_range = 10
|
||||
var/chosen_attack = 1 // chosen attack num
|
||||
var/list/attack_action_types = list()
|
||||
var/small_sprite_type
|
||||
|
||||
/mob/living/simple_animal/hostile/megafauna/Initialize(mapload)
|
||||
. = ..()
|
||||
apply_status_effect(STATUS_EFFECT_CRUSHERDAMAGETRACKING)
|
||||
ADD_TRAIT(src, TRAIT_NO_TELEPORT, MEGAFAUNA_TRAIT)
|
||||
for(var/action_type in attack_action_types)
|
||||
var/datum/action/innate/megafauna_attack/attack_action = new action_type()
|
||||
attack_action.Grant(src)
|
||||
if(small_sprite_type)
|
||||
var/datum/action/small_sprite/small_action = new small_sprite_type()
|
||||
small_action.Grant(src)
|
||||
|
||||
/mob/living/simple_animal/hostile/megafauna/Destroy()
|
||||
QDEL_NULL(internal)
|
||||
. = ..()
|
||||
|
||||
/mob/living/simple_animal/hostile/megafauna/Moved()
|
||||
if(nest && nest.parent && get_dist(nest.parent, src) > nest_range)
|
||||
var/turf/closest = get_turf(nest.parent)
|
||||
for(var/i = 1 to nest_range)
|
||||
closest = get_step(closest, get_dir(closest, src))
|
||||
forceMove(closest) // someone teleported out probably and the megafauna kept chasing them
|
||||
target = null
|
||||
return
|
||||
return ..()
|
||||
|
||||
/mob/living/simple_animal/hostile/megafauna/death(gibbed)
|
||||
if(health > 0)
|
||||
return
|
||||
@@ -134,3 +162,21 @@
|
||||
SSmedals.SetScore(BOSS_SCORE, C, 1)
|
||||
SSmedals.SetScore(score_type, C, 1)
|
||||
return TRUE
|
||||
|
||||
/datum/action/innate/megafauna_attack
|
||||
name = "Megafauna Attack"
|
||||
icon_icon = 'icons/mob/actions/actions_animal.dmi'
|
||||
button_icon_state = ""
|
||||
var/mob/living/simple_animal/hostile/megafauna/M
|
||||
var/chosen_message
|
||||
var/chosen_attack_num = 0
|
||||
|
||||
/datum/action/innate/megafauna_attack/Grant(mob/living/L)
|
||||
if(istype(L, /mob/living/simple_animal/hostile/megafauna))
|
||||
M = L
|
||||
return ..()
|
||||
return FALSE
|
||||
|
||||
/datum/action/innate/megafauna_attack/Activate()
|
||||
M.chosen_attack = chosen_attack_num
|
||||
to_chat(M, chosen_message)
|
||||
|
||||
@@ -0,0 +1,201 @@
|
||||
/*
|
||||
Difficulty: Hard
|
||||
*/
|
||||
|
||||
/mob/living/simple_animal/hostile/megafauna/wendigo
|
||||
name = "wendigo"
|
||||
desc = "A mythological man-eating legendary creature, you probably aren't going to survive this."
|
||||
health = 2500
|
||||
maxHealth = 2500
|
||||
icon_state = "wendigo"
|
||||
icon_living = "wendigo"
|
||||
icon_dead = "wendigo_dead"
|
||||
icon = 'icons/mob/icemoon/64x64megafauna.dmi'
|
||||
attacktext = "claws"
|
||||
attack_sound = 'sound/magic/demon_attack1.ogg'
|
||||
weather_immunities = list("snow")
|
||||
speak_emote = list("roars")
|
||||
armour_penetration = 40
|
||||
melee_damage_lower = 40
|
||||
melee_damage_upper = 40
|
||||
vision_range = 9
|
||||
aggro_vision_range = 18 // man-eating for a reason
|
||||
speed = 8
|
||||
move_to_delay = 8
|
||||
rapid_melee = 16 // every 1/8 second
|
||||
melee_queue_distance = 20 // as far as possible really, need this because of charging and teleports
|
||||
ranged = TRUE
|
||||
pixel_x = -16
|
||||
loot = list(/obj/item/wendigo_blood)
|
||||
crusher_loot = list(/obj/item/wendigo_blood, /obj/item/crusher_trophy/demon_claws)
|
||||
wander = FALSE
|
||||
del_on_death = TRUE
|
||||
blood_volume = BLOOD_VOLUME_NORMAL
|
||||
deathmessage = "falls, shaking the ground around it"
|
||||
deathsound = 'sound/effects/gravhit.ogg'
|
||||
attack_action_types = list(/datum/action/innate/megafauna_attack/heavy_stomp,
|
||||
/datum/action/innate/megafauna_attack/teleport,
|
||||
/datum/action/innate/megafauna_attack/disorienting_scream)
|
||||
/// Saves the turf the megafauna was created at (spawns exit portal here)
|
||||
var/turf/starting
|
||||
/// Range for wendigo stomping when it moves
|
||||
var/stomp_range = 1
|
||||
/// Stores directions the mob is moving, then calls that a move has fully ended when these directions are removed in moved
|
||||
var/stored_move_dirs = 0
|
||||
/// If the wendigo is allowed to move
|
||||
var/can_move = TRUE
|
||||
|
||||
/datum/action/innate/megafauna_attack/heavy_stomp
|
||||
name = "Heavy Stomp"
|
||||
icon_icon = 'icons/mob/actions/actions_items.dmi'
|
||||
button_icon_state = "sniper_zoom"
|
||||
chosen_message = "<span class='colossus'>You are now stomping the ground around you.</span>"
|
||||
chosen_attack_num = 1
|
||||
|
||||
/datum/action/innate/megafauna_attack/teleport
|
||||
name = "Teleport"
|
||||
icon_icon = 'icons/effects/bubblegum.dmi'
|
||||
button_icon_state = "smack ya one"
|
||||
chosen_message = "<span class='colossus'>You are now teleporting at the target you click on.</span>"
|
||||
chosen_attack_num = 2
|
||||
|
||||
/datum/action/innate/megafauna_attack/disorienting_scream
|
||||
name = "Disorienting Scream"
|
||||
icon_icon = 'icons/turf/walls/wall.dmi'
|
||||
button_icon_state = "wall"
|
||||
chosen_message = "<span class='colossus'>You are now screeching, disorienting targets around you.</span>"
|
||||
chosen_attack_num = 3
|
||||
|
||||
/mob/living/simple_animal/hostile/megafauna/wendigo/Initialize()
|
||||
. = ..()
|
||||
starting = get_turf(src)
|
||||
|
||||
/mob/living/simple_animal/hostile/megafauna/wendigo/OpenFire()
|
||||
SetRecoveryTime(0, 100)
|
||||
if(health <= maxHealth*0.5)
|
||||
stomp_range = 2
|
||||
speed = 6
|
||||
move_to_delay = 6
|
||||
else
|
||||
stomp_range = initial(stomp_range)
|
||||
speed = initial(speed)
|
||||
move_to_delay = initial(move_to_delay)
|
||||
|
||||
if(client)
|
||||
switch(chosen_attack)
|
||||
if(1)
|
||||
heavy_stomp()
|
||||
if(2)
|
||||
teleport()
|
||||
if(3)
|
||||
disorienting_scream()
|
||||
return
|
||||
|
||||
chosen_attack = rand(1, 3)
|
||||
switch(chosen_attack)
|
||||
if(1)
|
||||
heavy_stomp()
|
||||
if(2)
|
||||
teleport()
|
||||
if(3)
|
||||
disorienting_scream()
|
||||
|
||||
/mob/living/simple_animal/hostile/megafauna/wendigo/Move(atom/newloc, direct)
|
||||
if(!can_move)
|
||||
return
|
||||
stored_move_dirs |= direct
|
||||
return ..()
|
||||
|
||||
/mob/living/simple_animal/hostile/megafauna/wendigo/Moved(atom/oldloc, direct)
|
||||
. = ..()
|
||||
stored_move_dirs &= ~direct
|
||||
if(!stored_move_dirs)
|
||||
INVOKE_ASYNC(src, .proc/ground_slam, stomp_range, 1)
|
||||
|
||||
/// Slams the ground around the wendigo throwing back enemies caught nearby
|
||||
/mob/living/simple_animal/hostile/megafauna/wendigo/proc/ground_slam(range, delay)
|
||||
var/turf/orgin = get_turf(src)
|
||||
var/list/all_turfs = RANGE_TURFS(range, orgin)
|
||||
for(var/i = 0 to range)
|
||||
for(var/turf/T in all_turfs)
|
||||
if(get_dist(orgin, T) > i)
|
||||
continue
|
||||
playsound(T,'sound/effects/bamf.ogg', 600, TRUE, 10)
|
||||
new /obj/effect/temp_visual/small_smoke/halfsecond(T)
|
||||
for(var/mob/living/L in T)
|
||||
if(L == src || L.throwing)
|
||||
continue
|
||||
to_chat(L, "<span class='userdanger'>[src]'s ground slam shockwave sends you flying!</span>")
|
||||
var/turf/thrownat = get_ranged_target_turf_direct(src, L, 8, rand(-10, 10))
|
||||
L.throw_at(thrownat, 8, 2, src, TRUE) //, force = MOVE_FORCE_OVERPOWERING, gentle = TRUE)
|
||||
L.apply_damage(20, BRUTE)
|
||||
shake_camera(L, 2, 1)
|
||||
all_turfs -= T
|
||||
sleep(delay)
|
||||
|
||||
/// Larger but slower ground stomp
|
||||
/mob/living/simple_animal/hostile/megafauna/wendigo/proc/heavy_stomp()
|
||||
can_move = FALSE
|
||||
ground_slam(5, 2)
|
||||
SetRecoveryTime(0, 0)
|
||||
can_move = TRUE
|
||||
|
||||
/// Teleports to a location 4 turfs away from the enemy in view
|
||||
/mob/living/simple_animal/hostile/megafauna/wendigo/proc/teleport()
|
||||
var/list/possible_ends = list()
|
||||
for(var/turf/T in view(4, target.loc) - view(3, target.loc))
|
||||
if(isclosedturf(T))
|
||||
continue
|
||||
possible_ends |= T
|
||||
var/turf/end = pick(possible_ends)
|
||||
do_teleport(src, end, 0, channel=TELEPORT_CHANNEL_BLUESPACE, forced = TRUE)
|
||||
SetRecoveryTime(20, 0)
|
||||
|
||||
/// Shakes all nearby enemies screens and animates the wendigo shaking up and down
|
||||
/mob/living/simple_animal/hostile/megafauna/wendigo/proc/disorienting_scream()
|
||||
can_move = FALSE
|
||||
playsound(src, 'sound/magic/demon_dies.ogg', 600, FALSE, 10)
|
||||
animate(src, pixel_z = rand(5, 15), time = 1, loop = 6)
|
||||
animate(pixel_z = 0, time = 1)
|
||||
for(var/mob/living/L in get_hearers_in_view(7, src) - src)
|
||||
shake_camera(L, 30, 1)
|
||||
to_chat(L, "<span class='danger'>The wendigo screams loudly!</span>")
|
||||
SetRecoveryTime(30, 0)
|
||||
SLEEP_CHECK_DEATH(12)
|
||||
can_move = TRUE
|
||||
return
|
||||
|
||||
/mob/living/simple_animal/hostile/megafauna/wendigo/death(gibbed, list/force_grant)
|
||||
if(health > 0)
|
||||
return
|
||||
var/obj/effect/portal/permanent/one_way/exit = new /obj/effect/portal/permanent/one_way(starting)
|
||||
exit.id = "wendigo arena exit"
|
||||
exit.add_atom_colour(COLOR_RED_LIGHT, ADMIN_COLOUR_PRIORITY)
|
||||
exit.set_light(20, 1, LIGHT_COLOR_RED)
|
||||
return ..()
|
||||
|
||||
/obj/item/wendigo_blood
|
||||
name = "bottle of wendigo blood"
|
||||
desc = "You're not actually going to drink this, are you?"
|
||||
icon = 'icons/obj/wizard.dmi'
|
||||
icon_state = "vial"
|
||||
|
||||
/obj/item/wendigo_blood/attack_self(mob/living/user)
|
||||
if(!ishuman(user))
|
||||
return
|
||||
var/mob/living/carbon/human/H = user
|
||||
if(!H.mind)
|
||||
return
|
||||
to_chat(H, "<span class='danger'>Power courses through you! You can now shift your form at will.</span>")
|
||||
var/obj/effect/proc_holder/spell/targeted/shapeshift/polar_bear/P = new
|
||||
H.mind.AddSpell(P)
|
||||
playsound(H.loc,'sound/items/drink.ogg', rand(10,50), TRUE)
|
||||
qdel(src)
|
||||
|
||||
/obj/effect/proc_holder/spell/targeted/shapeshift/polar_bear
|
||||
name = "Polar Bear Form"
|
||||
desc = "Take on the shape of a polar bear."
|
||||
invocation = "RAAAAAAAAWR!"
|
||||
convert_damage = FALSE
|
||||
|
||||
shapeshift_type = /mob/living/simple_animal/hostile/asteroid/polarbear
|
||||
@@ -424,3 +424,29 @@
|
||||
l_pocket = /obj/item/reagent_containers/food/drinks/soda_cans/buzz_fuzz
|
||||
mask = /obj/item/clothing/mask/rat/bee
|
||||
. = ..()
|
||||
|
||||
// Snow Legion
|
||||
/mob/living/simple_animal/hostile/asteroid/hivelord/legion/snow
|
||||
name = "snow legion"
|
||||
desc = "You can still see what was once a human under the shifting snowy mass, clearly decorated by a clown."
|
||||
icon = 'icons/mob/icemoon/icemoon_monsters.dmi'
|
||||
icon_state = "snowlegion"
|
||||
icon_living = "snowlegion"
|
||||
icon_aggro = "snowlegion_alive"
|
||||
icon_dead = "snowlegion"
|
||||
crusher_loot = /obj/item/crusher_trophy/legion_skull
|
||||
loot = list(/obj/item/organ/regenerative_core/legion)
|
||||
brood_type = /mob/living/simple_animal/hostile/asteroid/hivelordbrood/legion/snow
|
||||
|
||||
/mob/living/simple_animal/hostile/asteroid/hivelord/legion/snow/tendril
|
||||
fromtendril = TRUE
|
||||
|
||||
// Snow Legion skull
|
||||
/mob/living/simple_animal/hostile/asteroid/hivelordbrood/legion/snow
|
||||
name = "snow legion"
|
||||
desc = "One of many."
|
||||
icon = 'icons/mob/icemoon/icemoon_monsters.dmi'
|
||||
icon_state = "snowlegion_head"
|
||||
icon_living = "snowlegion_head"
|
||||
icon_aggro = "snowlegion_head"
|
||||
icon_dead = "snowlegion_head"
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
/mob/living/simple_animal/hostile/asteroid/ice_demon
|
||||
name = "demonic watcher"
|
||||
desc = "A creature formed entirely out of ice, bluespace energy emanates from inside of it."
|
||||
icon = 'icons/mob/icemoon/icemoon_monsters.dmi'
|
||||
icon_state = "ice_demon"
|
||||
icon_living = "ice_demon"
|
||||
icon_dead = "ice_demon_dead"
|
||||
icon_gib = "syndicate_gib"
|
||||
mob_biotypes = MOB_ORGANIC|MOB_BEAST
|
||||
mouse_opacity = MOUSE_OPACITY_ICON
|
||||
speak_emote = list("telepathically cries")
|
||||
speed = 2
|
||||
move_to_delay = 2
|
||||
projectiletype = /obj/item/projectile/temp/basilisk/ice
|
||||
projectilesound = 'sound/weapons/pierce.ogg'
|
||||
ranged = TRUE
|
||||
ranged_message = "manifests ice"
|
||||
ranged_cooldown_time = 30
|
||||
minimum_distance = 3
|
||||
retreat_distance = 3
|
||||
maxHealth = 150
|
||||
health = 150
|
||||
obj_damage = 40
|
||||
melee_damage_lower = 15
|
||||
melee_damage_upper = 15
|
||||
attacktext = "slices"
|
||||
attack_sound = 'sound/weapons/bladeslice.ogg'
|
||||
vision_range = 9
|
||||
aggro_vision_range = 9
|
||||
move_force = MOVE_FORCE_VERY_STRONG
|
||||
move_resist = MOVE_FORCE_VERY_STRONG
|
||||
pull_force = MOVE_FORCE_VERY_STRONG
|
||||
del_on_death = TRUE
|
||||
loot = list(/obj/item/stack/ore/bluespace_crystal = 3)
|
||||
crusher_loot = /obj/item/crusher_trophy/watcher_wing/ice_wing
|
||||
deathmessage = "fades as the energies that tied it to this world dissipate."
|
||||
deathsound = 'sound/magic/demon_dies.ogg'
|
||||
stat_attack = UNCONSCIOUS
|
||||
movement_type = FLYING
|
||||
robust_searching = TRUE
|
||||
/// Distance the demon will teleport from the target
|
||||
var/teleport_distance = 3
|
||||
|
||||
/obj/item/projectile/temp/basilisk/ice
|
||||
name = "ice blast"
|
||||
damage = 5
|
||||
nodamage = FALSE
|
||||
temperature = -75
|
||||
|
||||
/mob/living/simple_animal/hostile/asteroid/ice_demon/OpenFire()
|
||||
if(teleport_distance <= 0)
|
||||
return ..()
|
||||
var/list/possible_ends = list()
|
||||
for(var/turf/T in view(teleport_distance, target.loc) - view(teleport_distance - 1, target.loc))
|
||||
if(isclosedturf(T))
|
||||
continue
|
||||
possible_ends |= T
|
||||
var/turf/end = pick(possible_ends)
|
||||
do_teleport(src, end, 0, channel=TELEPORT_CHANNEL_BLUESPACE, forced = TRUE)
|
||||
SLEEP_CHECK_DEATH(8)
|
||||
return ..()
|
||||
|
||||
/mob/living/simple_animal/hostile/asteroid/ice_demon/Life()
|
||||
. = ..()
|
||||
if(!. || target)
|
||||
return
|
||||
adjustHealth(-maxHealth*0.025)
|
||||
|
||||
/mob/living/simple_animal/hostile/asteroid/ice_demon/death(gibbed)
|
||||
move_force = MOVE_FORCE_DEFAULT
|
||||
move_resist = MOVE_RESIST_DEFAULT
|
||||
pull_force = PULL_FORCE_DEFAULT
|
||||
var/turf/T = get_turf(src)
|
||||
if(T && prob(5))
|
||||
new /obj/item/assembly/signaler/anomaly/bluespace(T)
|
||||
return ..()
|
||||
@@ -0,0 +1,54 @@
|
||||
/mob/living/simple_animal/hostile/asteroid/ice_whelp
|
||||
name = "ice whelp"
|
||||
desc = "The offspring of an ice drake, weak in comparison but still terrifying."
|
||||
icon = 'icons/mob/icemoon/icemoon_monsters.dmi'
|
||||
icon_state = "ice_whelp"
|
||||
icon_living = "ice_whelp"
|
||||
icon_dead = "ice_whelp_dead"
|
||||
mob_biotypes = MOB_ORGANIC|MOB_BEAST
|
||||
mouse_opacity = MOUSE_OPACITY_ICON
|
||||
friendly = "stares down"
|
||||
speak_emote = list("roars")
|
||||
speed = 30
|
||||
move_to_delay = 30
|
||||
ranged = TRUE
|
||||
ranged_cooldown_time = 40
|
||||
maxHealth = 350
|
||||
health = 350
|
||||
obj_damage = 40
|
||||
armour_penetration = 20
|
||||
melee_damage_lower = 20
|
||||
melee_damage_upper = 20
|
||||
attacktext = "chomps"
|
||||
attack_sound = 'sound/magic/demon_attack1.ogg'
|
||||
vision_range = 9
|
||||
aggro_vision_range = 9
|
||||
move_force = MOVE_FORCE_VERY_STRONG
|
||||
move_resist = MOVE_FORCE_VERY_STRONG
|
||||
pull_force = MOVE_FORCE_VERY_STRONG
|
||||
butcher_results = list(/obj/item/stack/ore/diamond = 3, /obj/item/stack/sheet/sinew = 2, /obj/item/stack/sheet/bone = 10, /obj/item/stack/sheet/animalhide/ashdrake = 1)
|
||||
loot = list()
|
||||
crusher_loot = /obj/item/crusher_trophy/tail_spike
|
||||
deathmessage = "collapses on it's side."
|
||||
deathsound = 'sound/magic/demon_dies.ogg'
|
||||
stat_attack = UNCONSCIOUS
|
||||
robust_searching = TRUE
|
||||
/// How far the whelps fire can go
|
||||
var/fire_range = 4
|
||||
|
||||
/mob/living/simple_animal/hostile/asteroid/ice_whelp/OpenFire()
|
||||
var/turf/T = get_ranged_target_turf_direct(src, target, fire_range)
|
||||
var/list/burn_turfs = getline(src, T) - get_turf(src)
|
||||
dragon_fire_line(src, burn_turfs)
|
||||
|
||||
/mob/living/simple_animal/hostile/asteroid/ice_whelp/Life()
|
||||
. = ..()
|
||||
if(!. || target)
|
||||
return
|
||||
adjustHealth(-maxHealth*0.025)
|
||||
|
||||
/mob/living/simple_animal/hostile/asteroid/ice_whelp/death(gibbed)
|
||||
move_force = MOVE_FORCE_DEFAULT
|
||||
move_resist = MOVE_RESIST_DEFAULT
|
||||
pull_force = PULL_FORCE_DEFAULT
|
||||
return ..()
|
||||
@@ -28,7 +28,7 @@
|
||||
|
||||
/mob/living/simple_animal/hostile/asteroid/Aggro()
|
||||
..()
|
||||
if(vision_range != aggro_vision_range)
|
||||
if(vision_range == aggro_vision_range && icon_aggro)
|
||||
icon_state = icon_aggro
|
||||
|
||||
/mob/living/simple_animal/hostile/asteroid/LoseAggro()
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
/mob/living/simple_animal/hostile/asteroid/polarbear
|
||||
name = "polar bear"
|
||||
desc = "An aggressive animal that defends it's territory with incredible power. These beasts don't run from their enemies."
|
||||
icon = 'icons/mob/icemoon/icemoon_monsters.dmi'
|
||||
icon_state = "polarbear"
|
||||
icon_living = "polarbear"
|
||||
icon_dead = "polarbear_dead"
|
||||
mob_biotypes = MOB_ORGANIC|MOB_BEAST
|
||||
mouse_opacity = MOUSE_OPACITY_ICON
|
||||
friendly = "growls at"
|
||||
speak_emote = list("growls")
|
||||
speed = 12
|
||||
move_to_delay = 12
|
||||
maxHealth = 300
|
||||
health = 300
|
||||
obj_damage = 40
|
||||
melee_damage_lower = 25
|
||||
melee_damage_upper = 25
|
||||
attacktext = "claws"
|
||||
attack_sound = 'sound/weapons/bladeslice.ogg'
|
||||
vision_range = 2 // don't aggro unless you basically antagonize it, though they will kill you worse than a goliath will
|
||||
aggro_vision_range = 9
|
||||
move_force = MOVE_FORCE_VERY_STRONG
|
||||
move_resist = MOVE_FORCE_VERY_STRONG
|
||||
pull_force = MOVE_FORCE_VERY_STRONG
|
||||
butcher_results = list(/obj/item/reagent_containers/food/snacks/meat/slab/bear = 3, /obj/item/stack/sheet/bone = 2)
|
||||
guaranteed_butcher_results = list(/obj/item/stack/sheet/animalhide/goliath_hide/polar_bear_hide = 1)
|
||||
loot = list()
|
||||
crusher_loot = /obj/item/crusher_trophy/goliath_tentacle
|
||||
stat_attack = UNCONSCIOUS
|
||||
robust_searching = TRUE
|
||||
/// Message for when the polar bear starts to attack faster
|
||||
var/aggressive_message_said = FALSE
|
||||
|
||||
/mob/living/simple_animal/hostile/asteroid/polarbear/adjustHealth(amount, updating_health = TRUE, forced = FALSE)
|
||||
. = ..()
|
||||
if(health > maxHealth*0.5)
|
||||
rapid_melee = initial(rapid_melee)
|
||||
return
|
||||
if(!aggressive_message_said && target)
|
||||
visible_message("<span class='danger'>The [name] gets an enraged look at [target]!</span>")
|
||||
aggressive_message_said = TRUE
|
||||
rapid_melee = 2
|
||||
|
||||
/mob/living/simple_animal/hostile/asteroid/polarbear/Life()
|
||||
. = ..()
|
||||
if(!. || target)
|
||||
return
|
||||
adjustHealth(-maxHealth*0.025)
|
||||
aggressive_message_said = FALSE
|
||||
|
||||
/mob/living/simple_animal/hostile/asteroid/polarbear/death(gibbed)
|
||||
move_force = MOVE_FORCE_DEFAULT
|
||||
move_resist = MOVE_RESIST_DEFAULT
|
||||
pull_force = PULL_FORCE_DEFAULT
|
||||
return ..()
|
||||
@@ -0,0 +1,57 @@
|
||||
/mob/living/simple_animal/hostile/asteroid/wolf
|
||||
name = "white wolf"
|
||||
desc = "A beast that survives by feasting on weaker opponents, they're much stronger with numbers."
|
||||
icon = 'icons/mob/icemoon/icemoon_monsters.dmi'
|
||||
icon_state = "whitewolf"
|
||||
icon_living = "whitewolf"
|
||||
icon_dead = "whitewolf_dead"
|
||||
mob_biotypes = MOB_ORGANIC|MOB_BEAST
|
||||
mouse_opacity = MOUSE_OPACITY_ICON
|
||||
friendly = "howls at"
|
||||
speak_emote = list("howls")
|
||||
speed = 5
|
||||
move_to_delay = 5
|
||||
maxHealth = 130
|
||||
health = 130
|
||||
obj_damage = 15
|
||||
melee_damage_lower = 7.5
|
||||
melee_damage_upper = 7.5
|
||||
rapid_melee = 2 // every second attack
|
||||
dodging = TRUE
|
||||
dodge_prob = 50
|
||||
attacktext = "bites"
|
||||
attack_sound = 'sound/weapons/bite.ogg'
|
||||
vision_range = 7
|
||||
aggro_vision_range = 7
|
||||
move_force = MOVE_FORCE_WEAK
|
||||
move_resist = MOVE_FORCE_WEAK
|
||||
pull_force = MOVE_FORCE_WEAK
|
||||
butcher_results = list(/obj/item/reagent_containers/food/snacks/meat/slab = 2, /obj/item/stack/sheet/sinew/wolf = 2, /obj/item/stack/sheet/bone = 2)
|
||||
loot = list()
|
||||
crusher_loot = /obj/item/crusher_trophy/watcher_wing
|
||||
stat_attack = UNCONSCIOUS
|
||||
robust_searching = TRUE
|
||||
/// Message for when the wolf decides to start running away
|
||||
var/retreat_message_said = FALSE
|
||||
|
||||
/mob/living/simple_animal/hostile/asteroid/wolf/Move(atom/newloc)
|
||||
if(newloc && newloc.z == z && (islava(newloc) || ischasm(newloc)))
|
||||
return FALSE
|
||||
return ..()
|
||||
|
||||
/mob/living/simple_animal/hostile/asteroid/wolf/adjustHealth(amount, updating_health = TRUE, forced = FALSE)
|
||||
. = ..()
|
||||
if(stat == DEAD || health > maxHealth*0.1)
|
||||
retreat_distance = initial(retreat_distance)
|
||||
return
|
||||
if(!retreat_message_said && target)
|
||||
visible_message("<span class='danger'>The [name] tries to flee from [target]!</span>")
|
||||
retreat_message_said = TRUE
|
||||
retreat_distance = 30
|
||||
|
||||
/mob/living/simple_animal/hostile/asteroid/wolf/Life()
|
||||
. = ..()
|
||||
if(!. || target)
|
||||
return
|
||||
adjustHealth(-maxHealth*0.025)
|
||||
retreat_message_said = FALSE
|
||||
@@ -1,14 +1,13 @@
|
||||
/mob/living/simple_animal/hostile/retaliate/clown
|
||||
name = "Clown"
|
||||
desc = "A denizen of clown planet."
|
||||
icon = 'icons/mob/simple_human.dmi'
|
||||
icon = 'icons/mob/clown_mobs.dmi'
|
||||
icon_state = "clown"
|
||||
icon_living = "clown"
|
||||
icon_dead = "clown_dead"
|
||||
icon_gib = "clown_gib"
|
||||
mob_biotypes = MOB_ORGANIC|MOB_HUMANOID
|
||||
turns_per_move = 5
|
||||
response_help = "pokes"
|
||||
response_disarm = "gently pushes aside"
|
||||
response_harm = "robusts"
|
||||
speak = list("HONK", "Honk!", "Welcome to clown planet!")
|
||||
@@ -21,7 +20,6 @@
|
||||
harm_intent_damage = 8
|
||||
melee_damage_lower = 10
|
||||
melee_damage_upper = 10
|
||||
attacktext = "attacks"
|
||||
attack_sound = 'sound/items/bikehorn.ogg'
|
||||
obj_damage = 0
|
||||
environment_smash = ENVIRONMENT_SMASH_NONE
|
||||
@@ -32,15 +30,245 @@
|
||||
minbodytemp = 270
|
||||
maxbodytemp = 370
|
||||
unsuitable_atmos_damage = 10
|
||||
|
||||
do_footstep = TRUE
|
||||
var/banana_time = 0 // If there's no time set it won't spawn.
|
||||
var/banana_type = /obj/item/grown/bananapeel
|
||||
var/attack_reagent
|
||||
|
||||
/mob/living/simple_animal/hostile/retaliate/clown/handle_temperature_damage()
|
||||
if(bodytemperature < minbodytemp)
|
||||
adjustBruteLoss(10)
|
||||
throw_alert("temp", /obj/screen/alert/cold, 2)
|
||||
else if(bodytemperature > maxbodytemp)
|
||||
adjustBruteLoss(15)
|
||||
throw_alert("temp", /obj/screen/alert/hot, 3)
|
||||
else
|
||||
clear_alert("temp")
|
||||
|
||||
/mob/living/simple_animal/hostile/retaliate/clown/attack_hand(mob/living/carbon/human/M)
|
||||
..()
|
||||
playsound(src.loc, 'sound/items/bikehorn.ogg', 50, 1)
|
||||
playsound(src.loc, 'sound/items/bikehorn.ogg', 50, TRUE)
|
||||
|
||||
/mob/living/simple_animal/hostile/retaliate/clown/Life()
|
||||
. = ..()
|
||||
if(banana_time && banana_time < world.time)
|
||||
var/turf/T = get_turf(src)
|
||||
var/list/adjacent = T.GetAtmosAdjacentTurfs(1)
|
||||
new banana_type(pick(adjacent))
|
||||
banana_time = world.time + rand(30,60)
|
||||
|
||||
/mob/living/simple_animal/hostile/retaliate/clown/AttackingTarget()
|
||||
. = ..()
|
||||
if(attack_reagent && . && isliving(target))
|
||||
var/mob/living/L = target
|
||||
if(L.reagents)
|
||||
L.reagents.add_reagent(attack_reagent, rand(1,5))
|
||||
|
||||
/mob/living/simple_animal/hostile/retaliate/clown/lube
|
||||
name = "Living Lube"
|
||||
desc = "A puddle of lube brought to life by the honkmother."
|
||||
icon_state = "lube"
|
||||
icon_living = "lube"
|
||||
turns_per_move = 1
|
||||
response_help = "dips a finger into"
|
||||
response_disarm = "gently scoops and pours aside"
|
||||
emote_see = list("bubbles", "oozes")
|
||||
loot = list(/obj/item/clothing/mask/gas/clown_hat, /obj/effect/particle_effect/foam)
|
||||
|
||||
/mob/living/simple_animal/hostile/retaliate/clown/lube/Initialize()
|
||||
. = ..()
|
||||
AddElement(/datum/element/snailcrawl)
|
||||
|
||||
/mob/living/simple_animal/hostile/retaliate/clown/banana
|
||||
name = "Clownana"
|
||||
desc = "A fusion of clown and banana DNA birthed from a botany experiment gone wrong."
|
||||
icon_state = "banana tree"
|
||||
icon_living = "banana tree"
|
||||
response_disarm = "peels"
|
||||
response_harm = "peels"
|
||||
turns_per_move = 1
|
||||
speak = list("HONK", "Honk!", "YA-HONK!!!")
|
||||
emote_see = list("honks", "bites into the banana", "plucks a banana off its head", "photosynthesizes")
|
||||
maxHealth = 120
|
||||
health = 120
|
||||
speed = -10
|
||||
loot = list(/obj/item/clothing/mask/gas/clown_hat, /obj/effect/gibspawner/human, /obj/item/soap, /obj/item/seeds/banana)
|
||||
banana_time = 20
|
||||
|
||||
/mob/living/simple_animal/hostile/retaliate/clown/honkling
|
||||
name = "Honkling"
|
||||
desc = "A divine being sent by the Honkmother to spread joy. It's not dangerous, but it's a bit of a nuisance."
|
||||
icon_state = "honkling"
|
||||
icon_living = "honkling"
|
||||
turns_per_move = 1
|
||||
speed = -10
|
||||
harm_intent_damage = 1
|
||||
melee_damage_lower = 1
|
||||
melee_damage_upper = 1
|
||||
attacktext = "cheers up"
|
||||
loot = list(/obj/item/clothing/mask/gas/clown_hat, /obj/effect/gibspawner/human, /obj/item/soap, /obj/item/seeds/banana/bluespace)
|
||||
banana_type = /obj/item/grown/bananapeel
|
||||
attack_reagent = /datum/reagent/consumable/laughter
|
||||
|
||||
/mob/living/simple_animal/hostile/retaliate/clown/fleshclown
|
||||
name = "Fleshclown"
|
||||
desc = "A being forged out of the pure essence of pranking, cursed into existence by a cruel maker."
|
||||
icon_state = "fleshclown"
|
||||
icon_living = "fleshclown"
|
||||
response_help = "reluctantly pokes"
|
||||
response_disarm = "sinks his hands into the spongy flesh of"
|
||||
response_harm = "cleanses the world of"
|
||||
speak = list("HONK", "Honk!", "I didn't ask for this", "I feel constant and horrible pain", "YA-HONK!!!", "this body is a merciless and unforgiving prison", "I was born out of mirthful pranking but I live in suffering")
|
||||
emote_see = list("honks", "sweats", "jiggles", "contemplates its existence")
|
||||
speak_chance = 5
|
||||
dextrous = TRUE
|
||||
ventcrawler = VENTCRAWLER_ALWAYS
|
||||
maxHealth = 140
|
||||
health = 140
|
||||
speed = -5
|
||||
melee_damage_upper = 15
|
||||
attacktext = "limply slaps"
|
||||
obj_damage = 5
|
||||
loot = list(/obj/item/clothing/suit/hooded/bloated_human, /obj/item/clothing/mask/gas/clown_hat, /obj/effect/gibspawner/human, /obj/item/soap)
|
||||
|
||||
/mob/living/simple_animal/hostile/retaliate/clown/longface
|
||||
name = "Longface"
|
||||
desc = "Often found walking into the bar."
|
||||
icon_state = "long face"
|
||||
icon_living = "long face"
|
||||
move_resist = INFINITY
|
||||
turns_per_move = 10
|
||||
response_help = "tries to awkwardly hug"
|
||||
response_disarm = "pushes the unwieldy frame of"
|
||||
response_harm = "tries to shut up"
|
||||
speak = list("YA-HONK!!!")
|
||||
emote_see = list("honks", "squeaks")
|
||||
speak_chance = 60
|
||||
maxHealth = 150
|
||||
health = 150
|
||||
pixel_x = -16
|
||||
speed = 10
|
||||
harm_intent_damage = 5
|
||||
melee_damage_lower = 5
|
||||
attacktext = "YA-HONKs"
|
||||
loot = list(/obj/item/clothing/mask/gas/clown_hat, /obj/effect/gibspawner/human, /obj/item/soap)
|
||||
|
||||
/mob/living/simple_animal/hostile/retaliate/clown/clownhulk
|
||||
name = "Honk Hulk"
|
||||
desc = "A cruel and fearsome clown. Don't make him angry."
|
||||
icon_state = "honkhulk"
|
||||
icon_living = "honkhulk"
|
||||
move_resist = INFINITY
|
||||
response_help = "tries desperately to appease"
|
||||
response_disarm = "foolishly pushes"
|
||||
response_harm = "angers"
|
||||
speak = list("HONK", "Honk!", "HAUAUANK!!!", "GUUURRRRAAAHHH!!!")
|
||||
emote_see = list("honks", "sweats", "grunts")
|
||||
speak_chance = 5
|
||||
maxHealth = 400
|
||||
health = 400
|
||||
pixel_x = -16
|
||||
speed = 2
|
||||
harm_intent_damage = 15
|
||||
melee_damage_lower = 15
|
||||
melee_damage_upper = 20
|
||||
attacktext = "pummels"
|
||||
obj_damage = 30
|
||||
environment_smash = ENVIRONMENT_SMASH_WALLS
|
||||
loot = list(/obj/item/clothing/mask/gas/clown_hat, /obj/effect/gibspawner/human, /obj/item/soap)
|
||||
|
||||
/mob/living/simple_animal/hostile/retaliate/clown/clownhulk/chlown
|
||||
name = "Chlown"
|
||||
desc = "A real lunkhead who somehow gets all the girls."
|
||||
icon_state = "chlown"
|
||||
icon_living = "chlown"
|
||||
response_help = "submits to"
|
||||
response_disarm = "tries to assert dominance over"
|
||||
response_harm = "makes a weak beta attack at"
|
||||
speak = list("HONK", "Honk!", "Bruh", "cheeaaaahhh?")
|
||||
emote_see = list("asserts his dominance", "emasculates everyone implicitly")
|
||||
maxHealth = 500
|
||||
health = 500
|
||||
speed = -2
|
||||
armour_penetration = 20
|
||||
attacktext = "steals the girlfriend of"
|
||||
attack_sound = 'sound/items/airhorn2.ogg'
|
||||
loot = list(/obj/item/clothing/mask/gas/clown_hat, /obj/effect/gibspawner/human, /obj/effect/particle_effect/foam, /obj/item/soap)
|
||||
|
||||
/mob/living/simple_animal/hostile/retaliate/clown/clownhulk/honcmunculus
|
||||
name = "Honkmunculus"
|
||||
desc = "A slender wiry figure of alchemical origin."
|
||||
icon_state = "honkmunculus"
|
||||
icon_living = "honkmunculus"
|
||||
response_help = "skeptically pokes"
|
||||
response_disarm = "pushes the unwieldy frame of"
|
||||
speak = list("honk")
|
||||
emote_see = list("squirms", "writhes")
|
||||
speak_chance = 1
|
||||
maxHealth = 200
|
||||
health = 200
|
||||
speed = -5
|
||||
harm_intent_damage = 5
|
||||
melee_damage_lower = 5
|
||||
melee_damage_upper = 10
|
||||
attacktext = "ferociously mauls"
|
||||
environment_smash = ENVIRONMENT_SMASH_NONE
|
||||
loot = list(/obj/item/clothing/mask/gas/clown_hat, /obj/effect/gibspawner/xeno/bodypartless, /obj/effect/particle_effect/foam, /obj/item/soap)
|
||||
attack_reagent = /datum/reagent/peaceborg_confuse
|
||||
|
||||
/mob/living/simple_animal/hostile/retaliate/clown/clownhulk/destroyer
|
||||
name = "The Destroyer"
|
||||
desc = "An ancient being born of arcane honking."
|
||||
icon_state = "destroyer"
|
||||
icon_living = "destroyer"
|
||||
response_disarm = "bounces off of"
|
||||
response_harm = "bounces off of"
|
||||
speak = list("HONK!!!", "The Honkmother is merciful, so I must act out her wrath.", "parce mihi ad beatus honkmother placet mihi ut peccata committere,", "DIE!!!")
|
||||
maxHealth = 400
|
||||
health = 400
|
||||
speed = 5
|
||||
harm_intent_damage = 30
|
||||
melee_damage_lower = 20
|
||||
melee_damage_upper = 40
|
||||
armour_penetration = 30
|
||||
stat_attack = UNCONSCIOUS
|
||||
attacktext = "acts out divine vengeance on"
|
||||
obj_damage = 50
|
||||
environment_smash = ENVIRONMENT_SMASH_RWALLS
|
||||
loot = list(/obj/item/clothing/mask/gas/clown_hat, /obj/effect/gibspawner/human, /obj/effect/particle_effect/foam, /obj/item/soap)
|
||||
|
||||
/mob/living/simple_animal/hostile/retaliate/clown/mutant
|
||||
name = "Unknown"
|
||||
desc = "Kill it for its own sake."
|
||||
icon_state = "mutant"
|
||||
icon_living = "mutant"
|
||||
move_resist = INFINITY
|
||||
turns_per_move = 10
|
||||
response_help = "reluctantly sinks a finger into"
|
||||
response_disarm = "squishes into"
|
||||
response_harm = "squishes into"
|
||||
speak = list("aaaaaahhhhuuhhhuhhhaaaaa", "AAAaaauuuaaAAAaauuhhh", "huuuuuh... hhhhuuuooooonnnnkk", "HuaUAAAnKKKK")
|
||||
emote_see = list("squirms", "writhes", "pulsates", "froths", "oozes")
|
||||
speak_chance = 10
|
||||
maxHealth = 130
|
||||
health = 130
|
||||
pixel_x = -16
|
||||
speed = -5
|
||||
harm_intent_damage = 10
|
||||
melee_damage_lower = 10
|
||||
melee_damage_upper = 20
|
||||
attacktext = "awkwardly flails at"
|
||||
loot = list(/obj/item/clothing/mask/gas/clown_hat, /obj/effect/gibspawner/xeno/bodypartless, /obj/item/soap, /obj/effect/gibspawner/generic, /obj/effect/gibspawner/generic/animal, /obj/effect/gibspawner/human/bodypartless, /obj/effect/gibspawner/human)
|
||||
|
||||
/mob/living/simple_animal/hostile/retaliate/clown/mutant/blob
|
||||
name = "Something that was once a clown"
|
||||
desc = "A grotesque bulging figure far mutated from it's original state."
|
||||
icon_state = "blob"
|
||||
icon_living = "blob"
|
||||
speak = list("hey, buddy", "HONK!!!", "H-h-h-H-HOOOOONK!!!!", "HONKHONKHONK!!!", "HEY, BUCKO, GET BACK HERE!!!", "HOOOOOOOONK!!!")
|
||||
emote_see = list("jiggles", "wobbles")
|
||||
health = 130
|
||||
mob_size = MOB_SIZE_LARGE
|
||||
speed = 20
|
||||
attacktext = "bounces off of"
|
||||
loot = list(/obj/item/clothing/mask/gas/clown_hat, /obj/effect/gibspawner/xeno/bodypartless, /obj/effect/particle_effect/foam, /obj/item/soap, /obj/effect/gibspawner/generic, /obj/effect/gibspawner/generic/animal, /obj/effect/gibspawner/human/bodypartless, /obj/effect/gibspawner/human)
|
||||
attack_reagent = /datum/reagent/toxin/mindbreaker
|
||||
|
||||
@@ -100,15 +100,16 @@
|
||||
if(mob.throwing)
|
||||
mob.throwing.finalize(FALSE)
|
||||
|
||||
var/atom/movable/P = mob.pulling
|
||||
if(P && !ismob(P) && P.density)
|
||||
mob.setDir(turn(mob.dir, 180))
|
||||
if(L.pulling && !(L.combat_flags & COMBAT_FLAG_COMBAT_ACTIVE))
|
||||
L.setDir(turn(L.dir, 180))
|
||||
|
||||
SEND_SIGNAL(mob, COMSIG_MOB_CLIENT_MOVE, src, direction, n, oldloc)
|
||||
|
||||
/// Process_Grab(): checks for grab, attempts to break if so. Return TRUE to prevent movement.
|
||||
/client/proc/Process_Grab()
|
||||
if(mob.pulledby)
|
||||
if((mob.pulledby == mob.pulling) && (mob.pulledby.grab_state == GRAB_PASSIVE)) //Don't autoresist passive grabs if we're grabbing them too.
|
||||
return
|
||||
if(mob.incapacitated(ignore_restraints = 1))
|
||||
move_delay = world.time + 10
|
||||
return TRUE
|
||||
|
||||
@@ -41,6 +41,9 @@ Contents:
|
||||
for(var/obj/effect/landmark/carpspawn/L in GLOB.landmarks_list)
|
||||
if(isturf(L.loc))
|
||||
spawn_locs += L.loc
|
||||
for(var/obj/effect/landmark/loneopspawn/L in GLOB.landmarks_list)
|
||||
if(isturf(L.loc))
|
||||
spawn_locs += L.loc
|
||||
if(!spawn_locs.len)
|
||||
return kill()
|
||||
spawn_loc = pick(spawn_locs)
|
||||
|
||||
@@ -196,12 +196,16 @@
|
||||
|
||||
switch(tdir)
|
||||
if(NORTH)
|
||||
pixel_x = 0
|
||||
pixel_y = 23
|
||||
if(SOUTH)
|
||||
pixel_x = 0
|
||||
pixel_y = -23
|
||||
if(EAST)
|
||||
pixel_y = 0
|
||||
pixel_x = 24
|
||||
if(WEST)
|
||||
pixel_y = 0
|
||||
pixel_x = -25
|
||||
|
||||
/obj/machinery/power/apc/Destroy()
|
||||
|
||||
@@ -91,6 +91,21 @@ By design, d1 is the smallest direction and d2 is the highest
|
||||
d1 = _d1
|
||||
d2 = _d2
|
||||
|
||||
if(dir != SOUTH)
|
||||
var/angle_to_turn = dir2angle(dir)
|
||||
if(angle_to_turn == 0 || angle_to_turn == 180)
|
||||
angle_to_turn += 180
|
||||
// direct dir set instead of setDir intentional
|
||||
dir = SOUTH
|
||||
if(d1)
|
||||
d1 = turn(d1, angle_to_turn)
|
||||
if(d2)
|
||||
d2 = turn(d2, angle_to_turn)
|
||||
if(d1 > d2)
|
||||
var/temp = d2
|
||||
d2 = d1
|
||||
d1 = temp
|
||||
|
||||
var/turf/T = get_turf(src) // hide if turf is not intact
|
||||
if(level==1)
|
||||
hide(T.intact)
|
||||
|
||||
@@ -66,8 +66,6 @@
|
||||
|
||||
#define SUPERMATTER_COUNTDOWN_TIME 30 SECONDS
|
||||
|
||||
#define SUPERMATTER_INTEGRITY_MULT 1.3 //(loss of integrity / 10) ^ 1.3
|
||||
|
||||
GLOBAL_DATUM(main_supermatter_engine, /obj/machinery/power/supermatter_crystal)
|
||||
|
||||
/obj/machinery/power/supermatter_crystal
|
||||
@@ -160,8 +158,6 @@ GLOBAL_DATUM(main_supermatter_engine, /obj/machinery/power/supermatter_crystal)
|
||||
var/datum/looping_sound/supermatter/soundloop
|
||||
|
||||
var/moveable = FALSE
|
||||
var/min_damage = 0
|
||||
var/power_calc = 0
|
||||
|
||||
/obj/machinery/power/supermatter_crystal/Initialize()
|
||||
. = ..()
|
||||
@@ -356,7 +352,6 @@ GLOBAL_DATUM(main_supermatter_engine, /obj/machinery/power/supermatter_crystal)
|
||||
|
||||
//capping damage
|
||||
damage = min(damage_archived + (DAMAGE_HARDCAP * explosion_point),damage)
|
||||
damage = max(min_damage, damage) //max integrity
|
||||
if(damage > damage_archived && prob(10))
|
||||
playsound(get_turf(src), 'sound/effects/empulse.ogg', 50, 1)
|
||||
|
||||
@@ -396,7 +391,7 @@ GLOBAL_DATUM(main_supermatter_engine, /obj/machinery/power/supermatter_crystal)
|
||||
|
||||
if(matter_power)
|
||||
var/removed_matter = max(matter_power/MATTER_POWER_CONVERSION, 40)
|
||||
power_calc = max(power_calc + removed_matter, 0)
|
||||
power = max(power + removed_matter, 0)
|
||||
matter_power = max(matter_power - removed_matter, 0)
|
||||
|
||||
var/temp_factor = 50
|
||||
@@ -409,7 +404,7 @@ GLOBAL_DATUM(main_supermatter_engine, /obj/machinery/power/supermatter_crystal)
|
||||
temp_factor = 30
|
||||
icon_state = base_icon_state
|
||||
|
||||
power_calc = max( (removed.temperature * temp_factor / T0C) * gasmix_power_ratio + power_calc, 0) //Total laser power plus an overload
|
||||
power = max( (removed.temperature * temp_factor / T0C) * gasmix_power_ratio + power, 0) //Total laser power plus an overload
|
||||
|
||||
if(prob(50))
|
||||
radiation_pulse(src, power * (1 + (tritiumcomp * TRITIUM_RADIOACTIVITY_MODIFIER) + ((pluoxiumcomp * PLUOXIUM_RADIOACTIVITY_MODIFIER) * pluoxiumbonus) * (power_transmission_bonus/(10-(bzcomp * BZ_RADIOACTIVITY_MODIFIER))))) // Rad Modifiers BZ(500%), Tritium(300%), and Pluoxium(-200%)
|
||||
@@ -441,19 +436,15 @@ GLOBAL_DATUM(main_supermatter_engine, /obj/machinery/power/supermatter_crystal)
|
||||
for(var/mob/living/carbon/human/l in view(src, HALLUCINATION_RANGE(power))) // If they can see it without mesons on. Bad on them.
|
||||
if(!istype(l.glasses, /obj/item/clothing/glasses/meson))
|
||||
var/D = sqrt(1 / max(1, get_dist(l, src)))
|
||||
l.hallucination += power_calc * config_hallucination_power * D
|
||||
l.hallucination += power * config_hallucination_power * D
|
||||
l.hallucination = clamp(0, 200, l.hallucination)
|
||||
|
||||
for(var/mob/living/l in range(src, round((power / 100) ** 0.25)))
|
||||
var/rads = (power / 10) * sqrt( 1 / max(get_dist(l, src),1) )
|
||||
l.rad_act(rads)
|
||||
|
||||
power_calc -= ((power_calc/500)**3) * powerloss_inhibitor
|
||||
if ((100-get_integrity()) < 75)
|
||||
power = (power_calc * ((100 - (0.15*(100-get_integrity()) - 5)**2) / 100)) + power_calc*0.1
|
||||
else
|
||||
power = power_calc * (((100-get_integrity())**((3*(100-get_integrity()))/1000) + 2*(100-get_integrity()))/100) //new and improved, more linear
|
||||
//power = power_calc * ((((100-get_integrity())**1.3)-(2*(100-get_integrity())))/100)
|
||||
power -= ((power/500)**3) * powerloss_inhibitor
|
||||
|
||||
if(power > POWER_PENALTY_THRESHOLD || damage > damage_penalty_point)
|
||||
|
||||
if(power > POWER_PENALTY_THRESHOLD)
|
||||
@@ -476,9 +467,7 @@ GLOBAL_DATUM(main_supermatter_engine, /obj/machinery/power/supermatter_crystal)
|
||||
supermatter_anomaly_gen(src, GRAVITATIONAL_ANOMALY, rand(5, 10))
|
||||
if(power > SEVERE_POWER_PENALTY_THRESHOLD && prob(2) || prob(0.3) && power > POWER_PENALTY_THRESHOLD)
|
||||
supermatter_anomaly_gen(src, PYRO_ANOMALY, rand(5, 10))
|
||||
if (damage - damage_archived > 0)
|
||||
min_damage += ((damage - damage_archived) / 9) * SUPERMATTER_INTEGRITY_MULT
|
||||
if(damage > (min_damage < 200 ? min_damage : 0) + warning_point) // while the core is still damaged and it's still worth noting its status
|
||||
if(damage > warning_point) // while the core is still damaged and it's still worth noting its status
|
||||
if((REALTIMEOFDAY - lastwarning) / 10 >= WARNING_DELAY)
|
||||
alarm()
|
||||
|
||||
@@ -517,7 +506,7 @@ GLOBAL_DATUM(main_supermatter_engine, /obj/machinery/power/supermatter_crystal)
|
||||
if(!istype(Proj.firer, /obj/machinery/power/emitter))
|
||||
investigate_log("has been hit by [Proj] fired by [key_name(Proj.firer)]", INVESTIGATE_SUPERMATTER)
|
||||
if(Proj.flag != "bullet")
|
||||
power_calc += Proj.damage * config_bullet_energy
|
||||
power += Proj.damage * config_bullet_energy
|
||||
if(!has_been_powered)
|
||||
investigate_log("has been powered for the first time.", INVESTIGATE_SUPERMATTER)
|
||||
message_admins("[src] has been powered for the first time [ADMIN_JMP(src)].")
|
||||
@@ -618,17 +607,6 @@ GLOBAL_DATUM(main_supermatter_engine, /obj/machinery/power/supermatter_crystal)
|
||||
to_chat(user, "<span class='notice'>You extract a sliver from \the [src]. \The [src] begins to react violently!</span>")
|
||||
new /obj/item/nuke_core/supermatter_sliver(drop_location())
|
||||
matter_power += 200
|
||||
if (istype(W, /obj/item/supermatterspray))
|
||||
var/obj/item/supermatterspray/S = W
|
||||
if (S.usesleft > 0 && do_after(user, 60, target=src))
|
||||
min_damage = max(0,min_damage - 10)
|
||||
to_chat(user, "<span class='notice'>You spray \the [src] with [S].</span>")
|
||||
playsound(user.loc,'sound/effects/spray2.ogg',50,1,-6)
|
||||
S.usesleft--
|
||||
if (prob(10))
|
||||
to_chat(user, "<span class='warning'>Your hands slip and you drop [S] into \the [src]!</span>")
|
||||
Consume(S)
|
||||
return
|
||||
else if(user.dropItemToGround(W))
|
||||
user.visible_message("<span class='danger'>As [user] touches \the [src] with \a [W], silence fills the room...</span>",\
|
||||
"<span class='userdanger'>You touch \the [src] with \the [W], and everything suddenly goes silent.</span>\n<span class='notice'>\The [W] flashes into dust as you flinch away from \the [src].</span>",\
|
||||
|
||||
@@ -33,7 +33,6 @@
|
||||
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!
|
||||
@@ -352,27 +351,12 @@
|
||||
return TRUE //Bullets don't drift in space
|
||||
|
||||
/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)
|
||||
|
||||
for(var/i in 1 to required_moves)
|
||||
pixel_move(1, FALSE)
|
||||
pixel_move(1, FALSE)
|
||||
|
||||
/obj/item/projectile/proc/fire(angle, atom/direct_target)
|
||||
if(fired_from)
|
||||
|
||||
@@ -182,18 +182,14 @@
|
||||
var/path = pick(/mob/living/simple_animal/hostile/carp,
|
||||
/mob/living/simple_animal/hostile/bear,
|
||||
/mob/living/simple_animal/hostile/mushroom,
|
||||
/mob/living/simple_animal/hostile/statue,
|
||||
/mob/living/simple_animal/hostile/retaliate/bat,
|
||||
/mob/living/simple_animal/hostile/retaliate/goat,
|
||||
/mob/living/simple_animal/hostile/killertomato,
|
||||
/mob/living/simple_animal/hostile/poison/giant_spider,
|
||||
/mob/living/simple_animal/hostile/poison/giant_spider/hunter,
|
||||
/mob/living/simple_animal/hostile/blob/blobbernaut/independent,
|
||||
/mob/living/simple_animal/hostile/carp/ranged,
|
||||
/mob/living/simple_animal/hostile/carp/ranged/chaos,
|
||||
/mob/living/simple_animal/hostile/asteroid/basilisk/watcher,
|
||||
/mob/living/simple_animal/hostile/asteroid/goliath/beast,
|
||||
/mob/living/simple_animal/hostile/headcrab,
|
||||
/mob/living/simple_animal/hostile/morph,
|
||||
/mob/living/simple_animal/hostile/stickman,
|
||||
/mob/living/simple_animal/hostile/stickman/dog,
|
||||
|
||||
@@ -207,8 +207,8 @@
|
||||
#define DELUXE_SELF_SPRAY 10
|
||||
#define DELUXE_SELF_INJECT 10
|
||||
|
||||
#define COMBAT_WAIT_SPRAY 0
|
||||
#define COMBAT_WAIT_INJECT 0
|
||||
#define COMBAT_WAIT_SPRAY 15
|
||||
#define COMBAT_WAIT_INJECT 15
|
||||
#define COMBAT_SELF_SPRAY 0
|
||||
#define COMBAT_SELF_INJECT 0
|
||||
|
||||
|
||||
@@ -61,7 +61,8 @@
|
||||
popup.open()
|
||||
|
||||
/obj/machinery/rnd/production/proc/calculate_efficiency()
|
||||
efficiency_coeff = 1
|
||||
var/total_manip_rating = 0
|
||||
var/manips = 0
|
||||
if(reagents) //If reagents/materials aren't initialized, don't bother, we'll be doing this again after reagents init anyways.
|
||||
reagents.maximum_volume = 0
|
||||
for(var/obj/item/reagent_containers/glass/G in component_parts)
|
||||
@@ -72,11 +73,10 @@
|
||||
for(var/obj/item/stock_parts/matter_bin/M in component_parts)
|
||||
total_storage += M.rating * 75000
|
||||
materials.set_local_size(total_storage)
|
||||
var/total_rating = 0
|
||||
for(var/obj/item/stock_parts/manipulator/M in component_parts)
|
||||
total_rating += M.rating
|
||||
total_rating = max(1, total_rating)
|
||||
efficiency_coeff = total_rating
|
||||
total_manip_rating += M.rating
|
||||
manips++
|
||||
efficiency_coeff = STANDARD_PART_LEVEL_LATHE_COEFFICIENT(total_manip_rating / (manips? manips : 1))
|
||||
|
||||
/obj/machinery/rnd/production/examine(mob/user)
|
||||
. = ..()
|
||||
@@ -113,7 +113,7 @@
|
||||
// these types don't have their .materials set in do_print, so don't allow
|
||||
// them to be constructed efficiently
|
||||
var/ef = efficient_with(being_built.build_path) ? efficiency_coeff : 1
|
||||
return round(A / max(1, all_materials[mat] / ef))
|
||||
return round(A / max(1, all_materials[mat] * ef))
|
||||
|
||||
/obj/machinery/rnd/production/proc/efficient_with(path)
|
||||
return !ispath(path, /obj/item/stack/sheet) && !ispath(path, /obj/item/stack/ore/bluespace_crystal)
|
||||
@@ -155,24 +155,24 @@
|
||||
var/coeff = efficient_with(D.build_path) ? efficiency_coeff : 1
|
||||
var/list/efficient_mats = list()
|
||||
for(var/MAT in D.materials)
|
||||
efficient_mats[MAT] = D.materials[MAT]/coeff
|
||||
efficient_mats[MAT] = D.materials[MAT] * coeff
|
||||
if(!materials.mat_container.has_materials(efficient_mats, amount))
|
||||
say("Not enough materials to complete prototype[amount > 1? "s" : ""].")
|
||||
return FALSE
|
||||
for(var/R in D.reagents_list)
|
||||
if(!reagents.has_reagent(R, D.reagents_list[R]*amount/coeff))
|
||||
if(!reagents.has_reagent(R, D.reagents_list[R] * amount * coeff))
|
||||
say("Not enough reagents to complete prototype[amount > 1? "s" : ""].")
|
||||
return FALSE
|
||||
materials.mat_container.use_materials(efficient_mats, amount)
|
||||
materials.silo_log(src, "built", -amount, "[D.name]", efficient_mats)
|
||||
for(var/R in D.reagents_list)
|
||||
reagents.remove_reagent(R, D.reagents_list[R]*amount/coeff)
|
||||
reagents.remove_reagent(R, D.reagents_list[R] * amount * coeff)
|
||||
busy = TRUE
|
||||
if(production_animation)
|
||||
flick(production_animation, src)
|
||||
var/timecoeff = D.lathe_time_factor / efficiency_coeff
|
||||
addtimer(CALLBACK(src, .proc/reset_busy), (30 * timecoeff * amount) ** 0.5)
|
||||
addtimer(CALLBACK(src, .proc/do_print, D.build_path, amount, efficient_mats, D.dangerous_construction, usr), (32 * timecoeff * amount) ** 0.8)
|
||||
var/timecoeff = D.lathe_time_factor * efficiency_coeff
|
||||
addtimer(CALLBACK(src, .proc/reset_busy), (20 * timecoeff * amount) ** 0.5)
|
||||
addtimer(CALLBACK(src, .proc/do_print, D.build_path, amount, efficient_mats, D.dangerous_construction, usr), (20 * timecoeff * amount) ** 0.5)
|
||||
return TRUE
|
||||
|
||||
/obj/machinery/rnd/production/proc/search(string)
|
||||
|
||||
37
code/modules/ruins/icemoonruin_code/hotsprings.dm
Normal file
37
code/modules/ruins/icemoonruin_code/hotsprings.dm
Normal file
@@ -0,0 +1,37 @@
|
||||
GLOBAL_LIST_EMPTY(cursed_minds)
|
||||
|
||||
/**
|
||||
* Turns whoever enters into a mob
|
||||
*
|
||||
* If mob is chosen, turns the person into a random animal type
|
||||
* Once the spring is used, it cannot be used by the same mind ever again
|
||||
* After usage, teleports the user back to a random safe turf (so mobs are not killed by ice moon atmosphere)
|
||||
*
|
||||
*/
|
||||
|
||||
/turf/open/water/cursed_spring
|
||||
baseturfs = /turf/open/water/cursed_spring
|
||||
initial_gas_mix = ICEMOON_DEFAULT_ATMOS
|
||||
|
||||
/turf/open/water/cursed_spring/Entered(atom/movable/thing, atom/oldLoc)
|
||||
. = ..()
|
||||
if(!isliving(thing))
|
||||
return
|
||||
var/mob/living/L = thing
|
||||
if(!L.client)
|
||||
return
|
||||
if(GLOB.cursed_minds[L.mind])
|
||||
return
|
||||
GLOB.cursed_minds[L.mind] = TRUE
|
||||
RegisterSignal(L.mind, COMSIG_PARENT_QDELETING, .proc/remove_from_cursed)
|
||||
L = wabbajack(L, "animal") // Appearance randomization removed so citadel players don't get randomized into some ungodly ugly creature and complain
|
||||
var/turf/T = find_safe_turf()
|
||||
L.forceMove(T)
|
||||
to_chat(L, "<span class='notice'>You blink and find yourself in [get_area_name(T)].</span>")
|
||||
|
||||
/**
|
||||
* Deletes minds from the cursed minds list after their deletion
|
||||
*
|
||||
*/
|
||||
/turf/open/water/cursed_spring/proc/remove_from_cursed(datum/mind/M)
|
||||
GLOB.cursed_minds -= M
|
||||
33
code/modules/ruins/icemoonruin_code/library.dm
Normal file
33
code/modules/ruins/icemoonruin_code/library.dm
Normal file
@@ -0,0 +1,33 @@
|
||||
/obj/machinery/door/keycard/library
|
||||
name = "wooden door"
|
||||
desc = "A dusty, scratched door with a thick lock attached."
|
||||
icon = 'icons/obj/doors/puzzledoor/wood.dmi'
|
||||
puzzle_id = "library"
|
||||
open_message = "The door opens with a loud creak."
|
||||
|
||||
/obj/item/keycard/library
|
||||
name = "golden key"
|
||||
desc = "A dull, golden key."
|
||||
icon_state = "golden_key"
|
||||
puzzle_id = "library"
|
||||
|
||||
/obj/item/paper/crumpled/bloody/fluff/stations/lavaland/library/warning
|
||||
name = "ancient note"
|
||||
info = "<b>Here lies the vast collection of He Who Knows Ten Thousand Things. May all who pursue this knowledge for power be cursed a thousand times.</b>"
|
||||
|
||||
/obj/item/paper/crumpled/fluff/stations/lavaland/library/diary
|
||||
name = "diary entry 13"
|
||||
info = "It has been a week since the library was buried. I am so hungry that I can barely muster the energy to think, let alone write. The owl is gone."
|
||||
|
||||
/obj/item/paper/crumpled/fluff/stations/lavaland/library/diary2
|
||||
name = "diary entry 18"
|
||||
info = "I've lost track of time. I lack the strength to even pick up books off the shelves. To think, after all this time spent searching for the library, I will die before I can so much as graze the depth of its knowledge."
|
||||
|
||||
/obj/item/feather
|
||||
name = "feather"
|
||||
desc = "A dark, wilting feather. It seems as old as time."
|
||||
icon = 'icons/obj/objects.dmi'
|
||||
icon_state = "feather"
|
||||
force = 0
|
||||
throwforce = 0
|
||||
w_class = WEIGHT_CLASS_TINY
|
||||
@@ -557,7 +557,7 @@
|
||||
dwidth = 1
|
||||
width = 3
|
||||
height = 4
|
||||
var/target_area = /area/lavaland/surface/outdoors
|
||||
var/target_area = list(/area/lavaland/surface/outdoors, /area/icemoon/underground/unexplored/rivers)
|
||||
var/edge_distance = 16
|
||||
// Minimal distance from the map edge, setting this too low can result in shuttle landing on the edge and getting "sliced"
|
||||
|
||||
|
||||
@@ -270,7 +270,7 @@
|
||||
var/turf/landmark_turf = get_turf(locate(/obj/effect/landmark/shuttle_import) in GLOB.landmarks_list)
|
||||
S.load(landmark_turf, centered = TRUE, register = FALSE)
|
||||
|
||||
var/affected = S.get_affected_turfs(landmark_turf, centered=TRUE)
|
||||
var/affected = S.get_affected_turfs(landmark_turf, centered = TRUE)
|
||||
|
||||
var/found = 0
|
||||
// Search the turfs for docking ports
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
var/view_range = 7
|
||||
var/x_offset = 0
|
||||
var/y_offset = 0
|
||||
var/list/whitelist_turfs = list(/turf/open/space, /turf/open/floor/plating, /turf/open/lava)
|
||||
var/space_turfs_only = TRUE
|
||||
var/see_hidden = FALSE
|
||||
var/designate_time = 0
|
||||
@@ -22,6 +23,7 @@
|
||||
/obj/machinery/computer/camera_advanced/shuttle_docker/Initialize()
|
||||
. = ..()
|
||||
GLOB.navigation_computers += src
|
||||
whitelist_turfs = typecacheof(whitelist_turfs)
|
||||
|
||||
/obj/machinery/computer/camera_advanced/shuttle_docker/Destroy()
|
||||
. = ..()
|
||||
@@ -226,6 +228,11 @@
|
||||
if(!ispath(turf_type, /turf/open/space))
|
||||
return SHUTTLE_DOCKER_BLOCKED
|
||||
|
||||
if(length(whitelist_turfs))
|
||||
var/turf_type = hidden_turf_info ? hidden_turf_info[2] : T.type
|
||||
if(!is_type_in_typecache(turf_type, whitelist_turfs))
|
||||
return SHUTTLE_DOCKER_BLOCKED
|
||||
|
||||
// Checking for overlapping dock boundaries
|
||||
for(var/i in 1 to overlappers.len)
|
||||
var/obj/docking_port/port = overlappers[i]
|
||||
|
||||
@@ -62,6 +62,8 @@
|
||||
view_range = 13
|
||||
x_offset = -7
|
||||
y_offset = -1
|
||||
space_turfs_only = FALSE
|
||||
whitelist_turfs = list(/turf/open/space, /turf/open/floor/plating, /turf/open/lava, /turf/closed/mineral)
|
||||
see_hidden = TRUE
|
||||
|
||||
#undef SYNDICATE_CHALLENGE_TIMER
|
||||
#undef SYNDICATE_CHALLENGE_TIMER
|
||||
|
||||
Reference in New Issue
Block a user