Merge remote-tracking branch 'citadel/master' into stuff
@@ -21911,7 +21911,7 @@
|
||||
/obj/machinery/atmospherics/pipe/simple/supply/hidden{
|
||||
dir = 4
|
||||
},
|
||||
/obj/machinery/computer/arcade/minesweeper,
|
||||
/obj/machinery/vending/wardrobe/cap_wardrobe,
|
||||
/turf/open/floor/wood,
|
||||
/area/crew_quarters/heads/captain)
|
||||
"bdj" = (
|
||||
@@ -55854,6 +55854,10 @@
|
||||
},
|
||||
/turf/open/floor/plasteel,
|
||||
/area/crew_quarters/dorms)
|
||||
"nZL" = (
|
||||
/obj/machinery/computer/arcade/minesweeper,
|
||||
/turf/open/floor/wood,
|
||||
/area/crew_quarters/heads/captain)
|
||||
"oax" = (
|
||||
/obj/structure/table/wood/fancy,
|
||||
/obj/machinery/light,
|
||||
@@ -90865,7 +90869,7 @@ aVv
|
||||
aXg
|
||||
aYF
|
||||
aZV
|
||||
bbw
|
||||
nZL
|
||||
bcn
|
||||
tgH
|
||||
ben
|
||||
|
||||
@@ -62353,7 +62353,7 @@
|
||||
/turf/open/floor/wood,
|
||||
/area/crew_quarters/heads/captain/private)
|
||||
"cbd" = (
|
||||
/obj/effect/landmark/event_spawn,
|
||||
/obj/machinery/vending/wardrobe/cap_wardrobe,
|
||||
/turf/open/floor/wood,
|
||||
/area/crew_quarters/heads/captain/private)
|
||||
"cbe" = (
|
||||
@@ -127169,6 +127169,13 @@
|
||||
dir = 9
|
||||
},
|
||||
/area/science/circuit)
|
||||
"rjr" = (
|
||||
/obj/machinery/atmospherics/pipe/manifold/supply/hidden{
|
||||
dir = 1
|
||||
},
|
||||
/obj/effect/landmark/event_spawn,
|
||||
/turf/open/floor/wood,
|
||||
/area/crew_quarters/heads/captain/private)
|
||||
"rqh" = (
|
||||
/obj/structure/lattice,
|
||||
/turf/open/space/basic,
|
||||
@@ -169426,7 +169433,7 @@ bUO
|
||||
bXf
|
||||
bUQ
|
||||
cbe
|
||||
ccP
|
||||
rjr
|
||||
ceH
|
||||
cgu
|
||||
cij
|
||||
|
||||
@@ -28355,6 +28355,7 @@
|
||||
/obj/structure/disposalpipe/segment{
|
||||
dir = 6
|
||||
},
|
||||
/obj/machinery/vending/wardrobe/cap_wardrobe,
|
||||
/turf/open/floor/carpet,
|
||||
/area/crew_quarters/heads/captain/private)
|
||||
"bdL" = (
|
||||
|
||||
@@ -9830,6 +9830,7 @@
|
||||
/obj/structure/cable{
|
||||
icon_state = "0-2"
|
||||
},
|
||||
/obj/item/twohanded/required/kirbyplants/random,
|
||||
/turf/open/floor/carpet,
|
||||
/area/crew_quarters/heads/captain)
|
||||
"axT" = (
|
||||
@@ -11162,10 +11163,7 @@
|
||||
/obj/structure/extinguisher_cabinet{
|
||||
pixel_x = -24
|
||||
},
|
||||
/obj/item/twohanded/required/kirbyplants{
|
||||
icon_state = "plant-20";
|
||||
pixel_y = 3
|
||||
},
|
||||
/obj/machinery/vending/wardrobe/cap_wardrobe,
|
||||
/turf/open/floor/plasteel/dark,
|
||||
/area/crew_quarters/heads/captain)
|
||||
"aBl" = (
|
||||
@@ -28097,11 +28095,6 @@
|
||||
},
|
||||
/turf/open/floor/plasteel/dark,
|
||||
/area/science/robotics/lab)
|
||||
"boY" = (
|
||||
/obj/effect/turf_decal/bot,
|
||||
/obj/effect/landmark/start/cyborg,
|
||||
/turf/open/floor/plasteel,
|
||||
/area/science/robotics/lab)
|
||||
"boZ" = (
|
||||
/obj/structure/table,
|
||||
/obj/machinery/cell_charger,
|
||||
@@ -98811,8 +98804,8 @@ beI
|
||||
bkt
|
||||
blH
|
||||
bmO
|
||||
boY
|
||||
boY
|
||||
bnQ
|
||||
bnQ
|
||||
bqi
|
||||
bry
|
||||
bsZ
|
||||
@@ -99068,8 +99061,8 @@ bju
|
||||
bkt
|
||||
blI
|
||||
bmO
|
||||
boY
|
||||
boY
|
||||
bnQ
|
||||
bnQ
|
||||
bqi
|
||||
brz
|
||||
bta
|
||||
|
||||
@@ -34,6 +34,7 @@
|
||||
#define CAN_MASTURBATE_WITH (1<<5)
|
||||
#define MASTURBATE_LINKED_ORGAN (1<<6) //used to pass our mission to the linked organ
|
||||
#define CAN_CLIMAX_WITH (1<<7)
|
||||
#define GENITAL_CAN_AROUSE (1<<8)
|
||||
|
||||
#define COCK_SIZE_MIN 1
|
||||
#define COCK_SIZE_MAX 20
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#define TGS_EXTERNAL_CONFIGURATION
|
||||
#define TGS_V3_API
|
||||
#define TGS_DEFINE_AND_SET_GLOBAL(Name, Value) GLOBAL_VAR_INIT(##Name, ##Value); GLOBAL_PROTECT(##Name)
|
||||
#define TGS_READ_GLOBAL(Name) GLOB.##Name
|
||||
#define TGS_WRITE_GLOBAL(Name, Value) GLOB.##Name = ##Value
|
||||
|
||||
@@ -107,6 +107,22 @@
|
||||
var/commit //full sha of compiled commit
|
||||
var/origin_commit //full sha of last known remote commit. This may be null if the TGS repository is not currently tracking a remote branch
|
||||
|
||||
//represents a version of tgstation-server
|
||||
/datum/tgs_version
|
||||
var/suite //The suite version, can be >=3
|
||||
|
||||
//this group of variables can be null to represent a wild card
|
||||
var/major //The major version
|
||||
var/minor //The minor version
|
||||
var/patch //The patch version
|
||||
|
||||
var/raw_parameter //The unparsed parameter
|
||||
var/deprefixed_parameter //The version only bit of raw_parameter
|
||||
|
||||
//if the tgs_version is a wildcard version
|
||||
/datum/tgs_version/proc/Wildcard()
|
||||
return
|
||||
|
||||
//represents a merge of a GitHub pull request
|
||||
/datum/tgs_revision_information/test_merge
|
||||
var/number //pull request number
|
||||
@@ -155,22 +171,22 @@
|
||||
|
||||
//FUNCTIONS
|
||||
|
||||
//Returns the respective string version of the API
|
||||
//Returns the respective supported /datum/tgs_version of the API
|
||||
/world/proc/TgsMaximumAPIVersion()
|
||||
return
|
||||
|
||||
/world/proc/TgsMinimumAPIVersion()
|
||||
return
|
||||
|
||||
//Gets the current version of the server tools running the server
|
||||
/world/proc/TgsVersion()
|
||||
return
|
||||
|
||||
//Returns TRUE if the world was launched under the server tools and the API matches, FALSE otherwise
|
||||
//No function below this succeeds if it returns FALSE
|
||||
/world/proc/TgsAvailable()
|
||||
return
|
||||
|
||||
//Gets the current /datum/tgs_version of the server tools running the server
|
||||
/world/proc/TgsVersion()
|
||||
return
|
||||
|
||||
/world/proc/TgsInstanceName()
|
||||
return
|
||||
|
||||
|
||||
@@ -57,14 +57,11 @@ GLOBAL_LIST_EMPTY(ipc_antennas_list)
|
||||
|
||||
//Genitals and Arousal Lists
|
||||
GLOBAL_LIST_EMPTY(genitals_list)
|
||||
GLOBAL_LIST_EMPTY(cock_shapes_list)//global_lists.dm for the list initializations //Now also _DATASTRUCTURES globals.dm
|
||||
GLOBAL_LIST_EMPTY(cock_shapes_icons) //Associated list for names->icon_states for cockshapes.
|
||||
GLOBAL_LIST_EMPTY(cock_shapes_list)
|
||||
GLOBAL_LIST_EMPTY(gentlemans_organ_names)
|
||||
GLOBAL_LIST_EMPTY(balls_shapes_list)
|
||||
GLOBAL_LIST_EMPTY(balls_shapes_icons)
|
||||
GLOBAL_LIST_EMPTY(breasts_size_list)
|
||||
GLOBAL_LIST_EMPTY(breasts_shapes_list)
|
||||
GLOBAL_LIST_EMPTY(breasts_shapes_icons)
|
||||
GLOBAL_LIST_EMPTY(vagina_shapes_list)
|
||||
GLOBAL_LIST_INIT(cum_into_containers_list, list(/obj/item/reagent_containers/food/snacks/pie)) //Yer fuggin snowflake name list jfc
|
||||
GLOBAL_LIST_INIT(dick_nouns, list("dick","cock","member","shaft"))
|
||||
|
||||
@@ -51,26 +51,15 @@
|
||||
init_sprite_accessory_subtypes(/datum/sprite_accessory/antenna, GLOB.ipc_antennas_list, roundstart = TRUE)
|
||||
//genitals
|
||||
init_sprite_accessory_subtypes(/datum/sprite_accessory/penis, GLOB.cock_shapes_list)
|
||||
for(var/K in GLOB.cock_shapes_list)
|
||||
var/datum/sprite_accessory/penis/value = GLOB.cock_shapes_list[K]
|
||||
GLOB.cock_shapes_icons[K] = value.icon_state
|
||||
|
||||
init_sprite_accessory_subtypes(/datum/sprite_accessory/vagina, GLOB.vagina_shapes_list)
|
||||
init_sprite_accessory_subtypes(/datum/sprite_accessory/breasts, GLOB.breasts_shapes_list)
|
||||
init_sprite_accessory_subtypes(/datum/sprite_accessory/testicles, GLOB.balls_shapes_list)
|
||||
GLOB.breasts_size_list = list ("a", "b", "c", "d", "e") //We need the list to choose from initialized, but it's no longer a sprite_accessory thing.
|
||||
GLOB.gentlemans_organ_names = list("phallus", "willy", "dick", "prick", "member", "tool", "gentleman's organ",
|
||||
"cock", "wang", "knob", "dong", "joystick", "pecker", "johnson", "weenie", "tadger", "schlong", "thirsty ferret",
|
||||
"baloney pony", "schlanger", "Mutton dagger", "old blind bob","Hanging Johnny", "fishing rod", "Tally whacker", "polly rocket",
|
||||
"One eyed trouser trout", "Ding dong", "ankle spanker", "Pork sword", "engine cranker", "Harry hot dog", "Davy Crockett",
|
||||
"Kidney cracker", "Heat seeking moisture missile", "Giggle stick", "love whistle", "Tube steak", "Uncle Dick", "Purple helmet warrior")
|
||||
for(var/K in GLOB.breasts_shapes_list)
|
||||
var/datum/sprite_accessory/breasts/value = GLOB.breasts_shapes_list[K]
|
||||
GLOB.breasts_shapes_icons[K] = value.icon_state
|
||||
|
||||
init_sprite_accessory_subtypes(/datum/sprite_accessory/testicles, GLOB.balls_shapes_list)
|
||||
for(var/K in GLOB.balls_shapes_list)
|
||||
var/datum/sprite_accessory/testicles/value = GLOB.balls_shapes_list[K]
|
||||
GLOB.balls_shapes_icons[K] = value.icon_state
|
||||
|
||||
for(var/gpath in subtypesof(/obj/item/organ/genital))
|
||||
var/obj/item/organ/genital/G = gpath
|
||||
|
||||
@@ -1543,3 +1543,25 @@ GLOBAL_DATUM_INIT(dview_mob, /mob/dview, new)
|
||||
for(var/each_item in items_list)
|
||||
for(var/i in 1 to items_list[each_item])
|
||||
new each_item(where_to)
|
||||
|
||||
//sends a message to chat
|
||||
//config_setting should be one of the following
|
||||
//null - noop
|
||||
//empty string - use TgsTargetBroadcast with admin_only = FALSE
|
||||
//other string - use TgsChatBroadcast with the tag that matches config_setting, only works with TGS4, if using TGS3 the above method is used
|
||||
/proc/send2chat(message, config_setting)
|
||||
if(config_setting == null || !world.TgsAvailable())
|
||||
return
|
||||
var/datum/tgs_version/version = world.TgsVersion()
|
||||
if(config_setting == "" || version.suite == 3)
|
||||
world.TgsTargetedChatBroadcast(message, FALSE)
|
||||
return
|
||||
|
||||
var/list/channels_to_use = list()
|
||||
for(var/I in world.TgsChatChannelInfo())
|
||||
var/datum/tgs_chat_channel/channel = I
|
||||
if(channel.tag == config_setting)
|
||||
channels_to_use += channel
|
||||
|
||||
if(channels_to_use.len)
|
||||
world.TgsChatBroadcast()
|
||||
@@ -1,23 +1,26 @@
|
||||
|
||||
/**
|
||||
*This is the proc that handles the order of an item_attack.
|
||||
*The order of procs called is:
|
||||
*tool_act on the target. If it returns TRUE, the chain will be stopped.
|
||||
*pre_attack() on src. If this returns TRUE, the chain will be stopped.
|
||||
*attackby on the target. If it returns TRUE, the chain will be stopped.
|
||||
*and lastly
|
||||
*afterattack. The return value does not matter.
|
||||
*/
|
||||
/obj/item/proc/melee_attack_chain(mob/user, atom/target, params)
|
||||
if(item_flags & NO_ATTACK_CHAIN_SOFT_STAMCRIT)
|
||||
if(user.getStaminaLoss() >= STAMINA_SOFTCRIT)
|
||||
to_chat(user, "<span class='warning'>You are too exhausted to swing [src]!</span>")
|
||||
return
|
||||
if(!tool_attack_chain(user, target) && pre_attack(target, user, params))
|
||||
// Return 1 in attackby() to prevent afterattack() effects (when safely moving items for example)
|
||||
var/resolved = target.attackby(src, user, params)
|
||||
if(!resolved && target && !QDELETED(src))
|
||||
afterattack(target, user, 1, params) // 1: clicking something Adjacent
|
||||
|
||||
|
||||
//Checks if the item can work as a tool, calling the appropriate tool behavior on the target
|
||||
/obj/item/proc/tool_attack_chain(mob/user, atom/target)
|
||||
if(!tool_behaviour)
|
||||
return FALSE
|
||||
|
||||
return target.tool_act(user, src, tool_behaviour)
|
||||
|
||||
if(tool_behaviour && target.tool_act(user, src, tool_behaviour))
|
||||
return
|
||||
if(pre_attack(target, user, params))
|
||||
return
|
||||
if(target.attackby(src,user, params))
|
||||
return
|
||||
if(QDELETED(src) || QDELETED(target))
|
||||
return
|
||||
afterattack(target, user, TRUE, params)
|
||||
|
||||
// Called when the item is in the active hand, and clicked; alternately, there is an 'activate held object' verb or you can hit pagedown.
|
||||
/obj/item/proc/attack_self(mob/user)
|
||||
@@ -27,8 +30,8 @@
|
||||
|
||||
/obj/item/proc/pre_attack(atom/A, mob/living/user, params) //do stuff before attackby!
|
||||
if(SEND_SIGNAL(src, COMSIG_ITEM_PRE_ATTACK, A, user, params) & COMPONENT_NO_ATTACK)
|
||||
return FALSE
|
||||
return TRUE //return FALSE to avoid calling attackby after this proc does stuff
|
||||
return TRUE
|
||||
return FALSE //return TRUE to avoid calling attackby after this proc does stuff
|
||||
|
||||
// No comment
|
||||
/atom/proc/attackby(obj/item/W, mob/user, params)
|
||||
|
||||
@@ -392,6 +392,13 @@
|
||||
config_entry_value = 50
|
||||
|
||||
/datum/config_entry/flag/irc_announce_new_game
|
||||
deprecated_by = /datum/config_entry/string/chat_announce_new_game
|
||||
|
||||
/datum/config_entry/flag/irc_announce_new_game/DeprecationUpdate(value)
|
||||
return "" //default broadcast
|
||||
|
||||
/datum/config_entry/string/chat_announce_new_game
|
||||
config_entry_value = null
|
||||
|
||||
/datum/config_entry/flag/debug_admin_hrefs
|
||||
|
||||
|
||||
@@ -81,8 +81,8 @@ SUBSYSTEM_DEF(server_maint)
|
||||
co.ehjax_send(data = "roundrestart")
|
||||
if(server) //if you set a server location in config.txt, it sends you there instead of trying to reconnect to the same world address. -- NeoFite
|
||||
C << link("byond://[server]")
|
||||
var/tgsversion = world.TgsVersion()
|
||||
var/datum/tgs_version/tgsversion = world.TgsVersion()
|
||||
if(tgsversion)
|
||||
SSblackbox.record_feedback("text", "server_tools", 1, tgsversion)
|
||||
SSblackbox.record_feedback("text", "server_tools", 1, tgsversion.raw_parameter)
|
||||
|
||||
#undef PING_BUFFER_TIME
|
||||
|
||||
@@ -158,8 +158,7 @@ SUBSYSTEM_DEF(ticker)
|
||||
for(var/client/C in GLOB.clients)
|
||||
window_flash(C, ignorepref = TRUE) //let them know lobby has opened up.
|
||||
to_chat(world, "<span class='boldnotice'>Welcome to [station_name()]!</span>")
|
||||
if(CONFIG_GET(flag/irc_announce_new_game))
|
||||
world.TgsTargetedChatBroadcast("New round starting on [SSmapping.config.map_name]!", FALSE)
|
||||
send2chat("New round starting on [SSmapping.config.map_name]!", CONFIG_GET(string/chat_announce_new_game))
|
||||
current_state = GAME_STATE_PREGAME
|
||||
//Everyone who wants to be an observer is now spawned
|
||||
create_observers()
|
||||
|
||||
@@ -27,7 +27,8 @@
|
||||
|
||||
for(var/line in testmerge)
|
||||
var/datum/tgs_revision_information/test_merge/tm = line
|
||||
msg += "Test merge active of PR #[tm.number] commit [tm.commit]"
|
||||
msg += "Test merge active of PR #[tm.number] commit [tm.pull_request_commit]"
|
||||
SSblackbox.record_feedback("associative", "testmerged_prs", 1, list("number" = "[tm.number]", "commit" = "[tm.pull_request_commit]", "title" = "[tm.title]", "author" = "[tm.author]"))
|
||||
|
||||
if(commit && commit != originmastercommit)
|
||||
msg += "HEAD: [commit]"
|
||||
@@ -75,7 +76,8 @@
|
||||
else if(!pc)
|
||||
msg += "No commit information"
|
||||
if(world.TgsAvailable())
|
||||
msg += "Server tools version: [world.TgsVersion()]"
|
||||
var/datum/tgs_version/version = world.TgsVersion()
|
||||
msg += "Server tools version: [version.raw_parameter]"
|
||||
|
||||
// Game mode odds
|
||||
msg += "<br><b>Current Informational Settings:</b>"
|
||||
|
||||
@@ -2,13 +2,11 @@
|
||||
#define FONT_SIZE "5pt"
|
||||
#define FONT_COLOR "#09f"
|
||||
#define FONT_STYLE "Arial Black"
|
||||
#define MAX_TIMER 9000
|
||||
|
||||
#define PRESET_SHORT 1200
|
||||
#define PRESET_MEDIUM 1800
|
||||
#define PRESET_LONG 3000
|
||||
|
||||
#define MAX_TIMER 15 MINUTES
|
||||
|
||||
#define PRESET_SHORT 2 MINUTES
|
||||
#define PRESET_MEDIUM 3 MINUTES
|
||||
#define PRESET_LONG 5 MINUTES
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Brig Door control displays.
|
||||
@@ -25,7 +23,7 @@
|
||||
desc = "A remote control for a door."
|
||||
req_access = list(ACCESS_SECURITY)
|
||||
density = FALSE
|
||||
var/id = null // id of linked machinery/lockers
|
||||
var/id // id of linked machinery/lockers
|
||||
|
||||
var/activation_time = 0
|
||||
var/timer_duration = 0
|
||||
@@ -43,8 +41,6 @@
|
||||
Radio = new/obj/item/radio(src)
|
||||
Radio.listening = 0
|
||||
|
||||
/obj/machinery/door_timer/Initialize()
|
||||
. = ..()
|
||||
if(id != null)
|
||||
for(var/obj/machinery/door/window/brigdoor/M in urange(20, src))
|
||||
if (M.id == id)
|
||||
@@ -71,7 +67,7 @@
|
||||
return
|
||||
|
||||
if(timing)
|
||||
if(world.realtime - activation_time >= timer_duration)
|
||||
if(REALTIMEOFDAY - activation_time >= timer_duration)
|
||||
timer_end() // open doors, reset timer, clear status screen
|
||||
update_icon()
|
||||
|
||||
@@ -80,14 +76,13 @@
|
||||
..()
|
||||
update_icon()
|
||||
|
||||
|
||||
// open/closedoor checks if door_timer has power, if so it checks if the
|
||||
// linked door is open/closed (by density) then opens it/closes it.
|
||||
/obj/machinery/door_timer/proc/timer_start()
|
||||
if(stat & (NOPOWER|BROKEN))
|
||||
return 0
|
||||
|
||||
activation_time = world.realtime
|
||||
activation_time = REALTIMEOFDAY
|
||||
timing = TRUE
|
||||
|
||||
for(var/obj/machinery/door/window/brigdoor/door in targets)
|
||||
@@ -104,7 +99,6 @@
|
||||
C.update_icon()
|
||||
return 1
|
||||
|
||||
|
||||
/obj/machinery/door_timer/proc/timer_end(forced = FALSE)
|
||||
|
||||
if(stat & (NOPOWER|BROKEN))
|
||||
@@ -136,7 +130,7 @@
|
||||
|
||||
|
||||
/obj/machinery/door_timer/proc/time_left(seconds = FALSE)
|
||||
. = max(0,timer_duration - (activation_time ? world.realtime - activation_time : 0))
|
||||
. = max(0,timer_duration - (activation_time ? REALTIMEOFDAY - activation_time : 0))
|
||||
if(seconds)
|
||||
. /= 10
|
||||
|
||||
@@ -240,7 +234,7 @@
|
||||
preset_time = PRESET_LONG
|
||||
. = set_timer(preset_time)
|
||||
if(timing)
|
||||
activation_time = world.realtime
|
||||
activation_time = REALTIMEOFDAY
|
||||
else
|
||||
. = FALSE
|
||||
|
||||
|
||||
@@ -345,7 +345,7 @@ GLOBAL_LIST_INIT(transit_tube_recipes, list(
|
||||
A = get_turf(A)
|
||||
var/can_make_pipe = (isturf(A) || is_type_in_typecache(A, make_pipe_whitelist))
|
||||
|
||||
. = FALSE
|
||||
. = TRUE
|
||||
|
||||
if((mode & DESTROY_MODE) && istype(A, /obj/item/pipe) || istype(A, /obj/structure/disposalconstruct) || istype(A, /obj/structure/c_transit_tube) || istype(A, /obj/structure/c_transit_tube_pod) || istype(A, /obj/item/pipe_meter))
|
||||
to_chat(user, "<span class='notice'>You start destroying a pipe...</span>")
|
||||
|
||||
@@ -38,6 +38,7 @@
|
||||
|
||||
/obj/item/geiger_counter/Destroy()
|
||||
STOP_PROCESSING(SSobj, src)
|
||||
QDEL_NULL(soundloop)
|
||||
return ..()
|
||||
|
||||
/obj/item/geiger_counter/process()
|
||||
|
||||
@@ -138,7 +138,7 @@
|
||||
|
||||
/obj/item/hand_tele/pre_attack(atom/target, mob/user, params)
|
||||
if(try_dispel_portal(target, user))
|
||||
return FALSE
|
||||
return TRUE
|
||||
return ..()
|
||||
|
||||
/obj/item/hand_tele/proc/try_dispel_portal(atom/target, mob/user)
|
||||
|
||||
@@ -2,38 +2,21 @@
|
||||
name = "\proper captain's locker"
|
||||
req_access = list(ACCESS_CAPTAIN)
|
||||
icon_state = "cap"
|
||||
/obj/structure/closet/secure_closet/captains/PopulateContents()
|
||||
/obj/structure/closet/secure_closet/captains/PopulateContents() //Excess clothing and such can be found in the Captain's Wardrobe. You can also find this file in code/modules/vending/wardrobes.
|
||||
..()
|
||||
new /obj/item/clothing/suit/hooded/wintercoat/captain(src)
|
||||
new /obj/item/storage/backpack/captain(src)
|
||||
new /obj/item/storage/backpack/satchel/cap(src)
|
||||
new /obj/item/storage/backpack/duffelbag/captain(src)
|
||||
new /obj/item/clothing/neck/cloak/cap(src)
|
||||
new /obj/item/clothing/neck/petcollar(src)
|
||||
new /obj/item/clothing/neck/petcollar(src) //I considered removing the pet stuff too but eh, who knows. We might get Renault back. Plus I guess you could use that collar for... other means. Aren't you supposed to be guarding the disk?
|
||||
new /obj/item/pet_carrier(src)
|
||||
new /obj/item/clothing/shoes/sneakers/brown(src)
|
||||
new /obj/item/clothing/under/rank/captain(src)
|
||||
new /obj/item/clothing/under/rank/captain/skirt(src)
|
||||
new /obj/item/clothing/suit/armor/vest/capcarapace(src)
|
||||
new /obj/item/clothing/head/caphat(src)
|
||||
new /obj/item/clothing/under/captainparade(src)
|
||||
new /obj/item/clothing/suit/armor/vest/capcarapace/alt(src)
|
||||
new /obj/item/clothing/head/caphat/parade(src)
|
||||
new /obj/item/clothing/head/caphat/beret(src)
|
||||
new /obj/item/clothing/suit/captunic(src)
|
||||
new /obj/item/clothing/under/rank/captain/femformal(src) //citadel edit
|
||||
new /obj/item/clothing/head/crown/fancy(src)
|
||||
new /obj/item/cartridge/captain(src)
|
||||
new /obj/item/storage/box/silver_ids(src)
|
||||
new /obj/item/radio/headset/heads/captain/alt(src)
|
||||
new /obj/item/radio/headset/heads/captain(src)
|
||||
new /obj/item/clothing/glasses/sunglasses/gar/supergar(src)
|
||||
new /obj/item/clothing/gloves/color/captain(src)
|
||||
new /obj/item/storage/belt/sabre(src)
|
||||
new /obj/item/gun/energy/e_gun(src)
|
||||
new /obj/item/door_remote/captain(src)
|
||||
new /obj/item/storage/photo_album/Captain(src)
|
||||
new /obj/item/clothing/head/caphat/beret/white(src)
|
||||
|
||||
/obj/structure/closet/secure_closet/hop
|
||||
name = "\proper head of personnel's locker"
|
||||
|
||||
@@ -47,6 +47,12 @@
|
||||
if(manifest)
|
||||
tear_manifest(user)
|
||||
|
||||
/obj/structure/closet/crate/tool_interact(obj/item/W, mob/user)
|
||||
if(W.tool_behaviour == TOOL_WIRECUTTER && manifest)
|
||||
tear_manifest(user)
|
||||
return TRUE
|
||||
return ..()
|
||||
|
||||
/obj/structure/closet/crate/open(mob/living/user)
|
||||
. = ..()
|
||||
if(. && manifest)
|
||||
|
||||
@@ -18,7 +18,7 @@ GLOBAL_LIST(topic_status_cache)
|
||||
|
||||
make_datum_references_lists() //initialises global lists for referencing frequently used datums (so that we only ever do it once)
|
||||
|
||||
TgsNew()
|
||||
TgsNew(minimum_required_security_level = TGS_SECURITY_TRUSTED)
|
||||
|
||||
GLOB.revdata = new
|
||||
|
||||
|
||||
@@ -68,8 +68,8 @@
|
||||
|
||||
/obj/item/clockwork/replica_fabricator/pre_attack(atom/target, mob/living/user, params)
|
||||
if(!target || !user || !is_servant_of_ratvar(user) || istype(target, /obj/item/storage))
|
||||
return TRUE
|
||||
return fabricate(target, user)
|
||||
return ..()
|
||||
return !fabricate(target, user)
|
||||
|
||||
//A note here; return values are for if we CAN BE PUT ON A TABLE, not IF WE ARE SUCCESSFUL, unless no_table_check is TRUE
|
||||
/obj/item/clockwork/replica_fabricator/proc/fabricate(atom/target, mob/living/user, silent, no_table_check)
|
||||
|
||||
@@ -470,7 +470,7 @@
|
||||
|
||||
/obj/item/claymore/ctf/pre_attack(atom/target, mob/user, params)
|
||||
if(!is_ctf_target(target))
|
||||
return FALSE
|
||||
return TRUE
|
||||
return ..()
|
||||
|
||||
/obj/item/claymore/ctf/dropped()
|
||||
|
||||
@@ -105,6 +105,7 @@ datum/bounty/reagent/complex_drink/New()
|
||||
/datum/reagent/consumable/ethanol/hearty_punch,\
|
||||
/datum/reagent/consumable/ethanol/manhattan_proj,\
|
||||
/datum/reagent/consumable/ethanol/narsour,\
|
||||
/datum/reagent/consumable/ethanol/cogchamp,\
|
||||
/datum/reagent/consumable/ethanol/neurotoxin,\
|
||||
/datum/reagent/consumable/ethanol/patron,\
|
||||
/datum/reagent/consumable/ethanol/quadruple_sec,\
|
||||
|
||||
@@ -272,7 +272,7 @@
|
||||
/datum/outfit/wizard
|
||||
name = "Blue Wizard"
|
||||
|
||||
uniform = /obj/item/clothing/under/color/lightpurple
|
||||
uniform = /obj/item/clothing/under/color/lightpurple/trackless
|
||||
suit = /obj/item/clothing/suit/wizrobe
|
||||
shoes = /obj/item/clothing/shoes/sandal/magic
|
||||
ears = /obj/item/radio/headset
|
||||
|
||||
@@ -41,6 +41,10 @@
|
||||
item_color = "black"
|
||||
resistance_flags = NONE
|
||||
|
||||
/obj/item/clothing/under/color/black/trackless
|
||||
desc = "A black jumpsuit that has its sensors removed."
|
||||
has_sensor = NO_SENSORS
|
||||
|
||||
/obj/item/clothing/under/skirt/color/black
|
||||
name = "black jumpskirt"
|
||||
icon_state = "black_skirt"
|
||||
@@ -189,13 +193,16 @@
|
||||
item_state = "b_suit"
|
||||
item_color = "teal_skirt"
|
||||
|
||||
|
||||
/obj/item/clothing/under/color/lightpurple
|
||||
name = "purple jumpsuit"
|
||||
icon_state = "lightpurple"
|
||||
item_state = "p_suit"
|
||||
item_color = "lightpurple"
|
||||
|
||||
/obj/item/clothing/under/color/lightpurple/trackless
|
||||
desc = "A magically colored jumpsuit. No sensors are attached!"
|
||||
has_sensor = NO_SENSORS
|
||||
|
||||
/obj/item/clothing/under/skirt/color/lightpurple
|
||||
name = "lightpurple jumpskirt"
|
||||
icon_state = "lightpurple_skirt"
|
||||
|
||||
@@ -19,6 +19,13 @@ GLOBAL_VAR_INIT(total_runtimes_skipped, 0)
|
||||
log_world("The bug with recursion runtimes has been fixed. Please remove the snowflake check from world/Error in [__FILE__]:[__LINE__]")
|
||||
return //this will never happen.
|
||||
|
||||
else if(copytext(E.name,1,18) == "Out of resources!")//18 == length() of that string + 1
|
||||
log_world("BYOND out of memory. Restarting")
|
||||
log_game("BYOND out of memory. Restarting")
|
||||
TgsEndProcess()
|
||||
Reboot(reason = 1)
|
||||
return ..()
|
||||
|
||||
if (islist(stack_trace_storage))
|
||||
for (var/line in splittext(E.desc, "\n"))
|
||||
if (text2ascii(line) != 32)
|
||||
|
||||
@@ -585,6 +585,14 @@
|
||||
mix_message = "The mixture develops a sinister glow."
|
||||
mix_sound = 'sound/effects/singlebeat.ogg'
|
||||
|
||||
/datum/chemical_reaction/cogchamp
|
||||
name = "CogChamp"
|
||||
id = /datum/reagent/consumable/ethanol/cogchamp
|
||||
results = list(/datum/reagent/consumable/ethanol/cogchamp = 3)
|
||||
required_reagents = list(/datum/reagent/consumable/ethanol/cognac = 1, /datum/reagent/fuel = 1, /datum/reagent/consumable/ethanol/screwdrivercocktail = 1)
|
||||
mix_message = "You hear faint sounds of gears turning as it mixes."
|
||||
mix_sound = 'sound/effects/clockcult_gateway_closing.ogg'
|
||||
|
||||
/datum/chemical_reaction/quadruplesec
|
||||
name = "Quadruple Sec"
|
||||
id = /datum/reagent/consumable/ethanol/quadruple_sec
|
||||
|
||||
@@ -46,7 +46,7 @@
|
||||
/obj/item/resonator/pre_attack(atom/target, mob/user, params)
|
||||
if(check_allowed_items(target, 1))
|
||||
CreateResonance(target, user)
|
||||
return TRUE
|
||||
return ..()
|
||||
|
||||
//resonance field, crushes rock, damages mobs
|
||||
/obj/effect/temp_visual/resonance
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
from doing this unless you absolutely know what you are doing, and have defined a
|
||||
conversion in savefile.dm
|
||||
*/
|
||||
/proc/init_sprite_accessory_subtypes(prototype, list/L, list/male, list/female,var/roundstart = FALSE)//Roundstart argument builds a specific list for roundstart parts where some parts may be locked
|
||||
/proc/init_sprite_accessory_subtypes(prototype, list/L, list/male, list/female, roundstart = FALSE, skip_prototype = TRUE)//Roundstart argument builds a specific list for roundstart parts where some parts may be locked
|
||||
if(!istype(L))
|
||||
L = list()
|
||||
if(!istype(male))
|
||||
@@ -25,7 +25,7 @@
|
||||
female = list()
|
||||
|
||||
for(var/path in typesof(prototype))
|
||||
if(path == prototype)
|
||||
if(path == prototype && skip_prototype)
|
||||
continue
|
||||
if(roundstart)
|
||||
var/datum/sprite_accessory/P = path
|
||||
|
||||
@@ -581,6 +581,9 @@ GLOBAL_LIST_INIT(ballmer_windows_me_msg, list("Yo man, what if, we like, uh, put
|
||||
if(cultslurring)
|
||||
cultslurring = max(cultslurring-1, 0)
|
||||
|
||||
if(clockcultslurring)
|
||||
clockcultslurring = max(clockcultslurring-1, 0)
|
||||
|
||||
if(silent)
|
||||
silent = max(silent-1, 0)
|
||||
|
||||
|
||||
@@ -89,6 +89,7 @@
|
||||
var/stuttering = 0
|
||||
var/slurring = 0
|
||||
var/cultslurring = 0
|
||||
var/clockcultslurring = 0
|
||||
var/derpspeech = 0
|
||||
|
||||
var/list/implants = null
|
||||
|
||||
@@ -354,6 +354,9 @@ GLOBAL_LIST_INIT(department_radio_keys, list(
|
||||
|
||||
if(cultslurring)
|
||||
message = cultslur(message)
|
||||
|
||||
if(clockcultslurring)
|
||||
message = CLOCK_CULT_SLUR(message)
|
||||
|
||||
message = capitalize(message)
|
||||
|
||||
|
||||
@@ -147,6 +147,10 @@
|
||||
. += newletter
|
||||
return sanitize(.)
|
||||
|
||||
//Ratvarian Slurring!
|
||||
|
||||
#define CLOCK_CULT_SLUR(phrase) sanitize(text2ratvar(phrase))
|
||||
|
||||
///Adds stuttering to the message passed in
|
||||
/proc/stutter(phrase)
|
||||
phrase = html_decode(phrase)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/datum/outfit/ninja
|
||||
name = "Space Ninja"
|
||||
uniform = /obj/item/clothing/under/color/black
|
||||
uniform = /obj/item/clothing/under/color/black/trackless
|
||||
suit = /obj/item/clothing/suit/space/space_ninja
|
||||
glasses = /obj/item/clothing/glasses/night
|
||||
mask = /obj/item/clothing/mask/gas/space_ninja
|
||||
|
||||
@@ -15,15 +15,15 @@
|
||||
. = ..()
|
||||
|
||||
/obj/item/ammo_casing/energy/gravity/repulse
|
||||
projectile_type = /obj/item/projectile/gravityrepulse
|
||||
projectile_type = /obj/item/projectile/gravity/repulse
|
||||
select_name = "repulse"
|
||||
|
||||
/obj/item/ammo_casing/energy/gravity/attract
|
||||
projectile_type = /obj/item/projectile/gravityattract
|
||||
projectile_type = /obj/item/projectile/gravity/attract
|
||||
select_name = "attract"
|
||||
|
||||
/obj/item/ammo_casing/energy/gravity/chaos
|
||||
projectile_type = /obj/item/projectile/gravitychaos
|
||||
projectile_type = /obj/item/projectile/gravity/chaos
|
||||
select_name = "chaos"
|
||||
|
||||
|
||||
|
||||
@@ -69,13 +69,10 @@
|
||||
if(gun)
|
||||
qdel(gun)
|
||||
|
||||
|
||||
|
||||
/obj/item/firing_pin/magic
|
||||
name = "magic crystal shard"
|
||||
desc = "A small enchanted shard which allows magical weapons to fire."
|
||||
|
||||
|
||||
// Test pin, works only near firing range.
|
||||
/obj/item/firing_pin/test_range
|
||||
name = "test-range firing pin"
|
||||
@@ -229,3 +226,16 @@
|
||||
if(gun)
|
||||
gun.pin = null
|
||||
return ..()
|
||||
|
||||
//Station Locked
|
||||
|
||||
/obj/item/firing_pin/away
|
||||
name = "station locked pin"
|
||||
desc = "A firing pin that only will fire when off the station."
|
||||
|
||||
/obj/item/firing_pin/away/pin_auth(mob/living/user)
|
||||
var/area/station_area = get_area(src)
|
||||
if(!station_area || is_station_level(station_area.z))
|
||||
to_chat(user, "<span class='warning'>The pin beeps, refusing to fire.</span>")
|
||||
return FALSE
|
||||
return TRUE
|
||||
|
||||
@@ -1,90 +1,65 @@
|
||||
/obj/item/projectile/gravityrepulse
|
||||
/obj/item/projectile/gravity
|
||||
name = "gravity bolt"
|
||||
icon = 'icons/effects/effects.dmi'
|
||||
icon_state = "chronofield"
|
||||
hitsound = 'sound/weapons/wave.ogg'
|
||||
damage = 0
|
||||
damage_type = BRUTE
|
||||
nodamage = TRUE
|
||||
var/power = 4
|
||||
var/list/thrown = list() //normally we wouldn't need this but incase on_hit is called multiple times, yeah.. this is a good idea.
|
||||
|
||||
/obj/item/projectile/gravity/Initialize(mapload)
|
||||
. = ..()
|
||||
var/obj/item/ammo_casing/energy/gravity/G = loc
|
||||
if(istype(G))
|
||||
power = min(G.gun.power, 15)
|
||||
|
||||
/obj/item/projectile/gravity/on_hit()
|
||||
. = ..()
|
||||
var/turf/T = get_turf(src)
|
||||
var/list/tothrow = list()
|
||||
var/list/cachedrange = range(T, power)
|
||||
for(var/mob/M in cachedrange)
|
||||
tothrow += M
|
||||
for(var/obj/O in cachedrange)
|
||||
tothrow += O
|
||||
var/safety = 50
|
||||
for(var/i in tothrow)
|
||||
if(!safety)
|
||||
break
|
||||
var/atom/movable/AM = i
|
||||
if((AM == src) || (AM == firer) || (AM.move_resist > MOVE_FORCE_EXTREMELY_STRONG) || AM.anchored || thrown[AM])
|
||||
continue
|
||||
thrown[AM] = TRUE
|
||||
safety--
|
||||
var/target = get_target(T, AM)
|
||||
if(!target)
|
||||
return FALSE
|
||||
AM.throw_at(target, power + 1, 1)
|
||||
for(var/turf/F in range(T,power))
|
||||
new /obj/effect/temp_visual/gravpush(F)
|
||||
|
||||
/obj/item/projectile/gravity/proc/get_target(turf/origin, atom/movable/AM)
|
||||
return origin
|
||||
|
||||
/obj/item/projectile/gravity/repulse
|
||||
name = "repulsion bolt"
|
||||
icon = 'icons/effects/effects.dmi'
|
||||
icon_state = "chronofield"
|
||||
hitsound = 'sound/weapons/wave.ogg'
|
||||
damage = 0
|
||||
damage_type = BRUTE
|
||||
nodamage = 1
|
||||
color = "#33CCFF"
|
||||
var/turf/T
|
||||
var/power = 4
|
||||
var/list/thrown_items = list()
|
||||
|
||||
/obj/item/projectile/gravityrepulse/Initialize()
|
||||
. = ..()
|
||||
var/obj/item/ammo_casing/energy/gravity/repulse/C = loc
|
||||
if(istype(C)) //Hard-coded maximum power so servers can't be crashed by trying to throw the entire Z level's items
|
||||
power = min(C.gun.power, 15)
|
||||
/obj/item/projectile/gravity/repulse/get_target(turf/origin, atom/movable/AM)
|
||||
return get_turf_in_angle(Get_Angle(origin, AM), origin, power)
|
||||
|
||||
/obj/item/projectile/gravityrepulse/on_hit()
|
||||
. = ..()
|
||||
T = get_turf(src)
|
||||
for(var/atom/movable/A in range(T, power))
|
||||
if(A == src || (firer && A == src.firer) || A.anchored || thrown_items[A])
|
||||
continue
|
||||
var/throwtarget = get_edge_target_turf(src, get_dir(src, get_step_away(A, src)))
|
||||
A.throw_at(throwtarget,power+1,1)
|
||||
thrown_items[A] = A
|
||||
for(var/turf/F in range(T,power))
|
||||
new /obj/effect/temp_visual/gravpush(F)
|
||||
|
||||
/obj/item/projectile/gravityattract
|
||||
/obj/item/projectile/gravity/attract
|
||||
name = "attraction bolt"
|
||||
icon = 'icons/effects/effects.dmi'
|
||||
icon_state = "chronofield"
|
||||
hitsound = 'sound/weapons/wave.ogg'
|
||||
damage = 0
|
||||
damage_type = BRUTE
|
||||
nodamage = 1
|
||||
color = "#FF6600"
|
||||
var/turf/T
|
||||
var/power = 4
|
||||
var/list/thrown_items = list()
|
||||
|
||||
/obj/item/projectile/gravityattract/Initialize()
|
||||
. = ..()
|
||||
var/obj/item/ammo_casing/energy/gravity/attract/C = loc
|
||||
if(istype(C)) //Hard-coded maximum power so servers can't be crashed by trying to throw the entire Z level's items
|
||||
power = min(C.gun.power, 15)
|
||||
/obj/item/projectile/gravity/attract/get_target(turf/origin, atom/movable/AM)
|
||||
return origin
|
||||
|
||||
/obj/item/projectile/gravityattract/on_hit()
|
||||
. = ..()
|
||||
T = get_turf(src)
|
||||
for(var/atom/movable/A in range(T, power))
|
||||
if(A == src || (firer && A == src.firer) || A.anchored || thrown_items[A])
|
||||
continue
|
||||
A.throw_at(T, power+1, 1)
|
||||
thrown_items[A] = A
|
||||
for(var/turf/F in range(T,power))
|
||||
new /obj/effect/temp_visual/gravpush(F)
|
||||
|
||||
/obj/item/projectile/gravitychaos
|
||||
/obj/item/projectile/gravity/chaos
|
||||
name = "gravitational blast"
|
||||
icon = 'icons/effects/effects.dmi'
|
||||
icon_state = "chronofield"
|
||||
hitsound = 'sound/weapons/wave.ogg'
|
||||
damage = 0
|
||||
damage_type = BRUTE
|
||||
nodamage = 1
|
||||
color = "#101010"
|
||||
var/turf/T
|
||||
var/power = 4
|
||||
var/list/thrown_items = list()
|
||||
|
||||
/obj/item/projectile/gravitychaos/Initialize()
|
||||
. = ..()
|
||||
var/obj/item/ammo_casing/energy/gravity/chaos/C = loc
|
||||
if(istype(C)) //Hard-coded maximum power so servers can't be crashed by trying to throw the entire Z level's items
|
||||
power = min(C.gun.power, 15)
|
||||
|
||||
/obj/item/projectile/gravitychaos/on_hit()
|
||||
. = ..()
|
||||
T = get_turf(src)
|
||||
for(var/atom/movable/A in range(T, power))
|
||||
if(A == src|| (firer && A == src.firer) || A.anchored || thrown_items[A])
|
||||
continue
|
||||
A.throw_at(get_edge_target_turf(A, pick(GLOB.cardinals)), power+1, 1)
|
||||
thrown_items[A] = A
|
||||
for(var/turf/Z in range(T,power))
|
||||
new /obj/effect/temp_visual/gravpush(Z)
|
||||
/obj/item/projectile/gravity/chaos/get_target(turf/origin, atom/movable/AM)
|
||||
return get_turf_in_angle(rand(0, 359), origin, power)
|
||||
|
||||
@@ -1513,6 +1513,23 @@ All effects don't start immediately, but rather get worse over time; the rate is
|
||||
M.stuttering = min(M.stuttering + 3, 3)
|
||||
..()
|
||||
|
||||
/datum/reagent/consumable/ethanol/cogchamp
|
||||
name = "CogChamp"
|
||||
description = "Now you can fill yourself with the power of Ratvar!"
|
||||
color = rgb(255, 201, 49)
|
||||
boozepwr = 10
|
||||
quality = DRINK_FANTASTIC
|
||||
taste_description = "a brass taste with a hint of oil"
|
||||
glass_icon_state = "cogchamp"
|
||||
glass_name = "CogChamp"
|
||||
glass_desc = "Not even Ratvar's Four Generals could withstand this! Qevax Jryy!"
|
||||
value = 8.13
|
||||
|
||||
/datum/reagent/consumable/ethanol/cogchamp/on_mob_life(mob/living/carbon/M)
|
||||
M.clockcultslurring = min(M.clockcultslurring + 3, 3)
|
||||
M.stuttering = min(M.stuttering + 3, 3)
|
||||
..()
|
||||
|
||||
/datum/reagent/consumable/ethanol/triple_sec
|
||||
name = "Triple Sec"
|
||||
description = "A sweet and vibrant orange liqueur."
|
||||
|
||||
@@ -423,9 +423,9 @@
|
||||
else
|
||||
unload_hypo(vial,user)
|
||||
|
||||
/obj/item/hypospray/mkii/AltClick(mob/living/user)
|
||||
/obj/item/hypospray/mkii/CtrlClick(mob/living/user)
|
||||
. = ..()
|
||||
if(user.canUseTopic(src, FALSE))
|
||||
if(user.canUseTopic(src, FALSE) && user.get_active_held_item(src))
|
||||
switch(mode)
|
||||
if(HYPO_SPRAY)
|
||||
mode = HYPO_INJECT
|
||||
@@ -437,7 +437,7 @@
|
||||
|
||||
/obj/item/hypospray/mkii/examine(mob/user)
|
||||
. = ..()
|
||||
. += "<span class='notice'><b>Alt-Click</b> it to toggle its mode from spraying to injecting and vice versa.</span>"
|
||||
. += "<span class='notice'><b>Ctrl-Click</b> it to toggle its mode from spraying to injecting and vice versa.</span>"
|
||||
|
||||
#undef HYPO_SPRAY
|
||||
#undef HYPO_INJECT
|
||||
|
||||
@@ -225,6 +225,16 @@
|
||||
category = list("Firing Pins")
|
||||
departmental_flags = DEPARTMENTAL_FLAG_SECURITY
|
||||
|
||||
/datum/design/pin_away
|
||||
name = "Station Locked Pin"
|
||||
desc = "This is a security firing pin which only authorizes users who are off station."
|
||||
id = "pin_away"
|
||||
build_type = PROTOLATHE
|
||||
materials = list(MAT_METAL = 1500, MAT_GLASS = 2000)
|
||||
build_path = /obj/item/firing_pin/away
|
||||
category = list("Firing Pins")
|
||||
departmental_flags = DEPARTMENTAL_FLAG_SECURITY
|
||||
|
||||
//////////////
|
||||
//Guns////////
|
||||
//////////////
|
||||
|
||||
@@ -22,7 +22,7 @@ If you create T5+ please take a pass at gene_modder.dm [L40]. Max_values MUST fi
|
||||
if(works_from_distance)
|
||||
user.Beam(T, icon_state = "rped_upgrade", time = 5)
|
||||
T.exchange_parts(user, src)
|
||||
return FALSE
|
||||
return TRUE
|
||||
return ..()
|
||||
|
||||
/obj/item/storage/part_replacer/afterattack(obj/machinery/T, mob/living/user, adjacent, params)
|
||||
|
||||
@@ -666,7 +666,7 @@
|
||||
display_name = "Weapon Development Technology"
|
||||
description = "Our researchers have found new to weaponize just about everything now."
|
||||
prereq_ids = list("engineering")
|
||||
design_ids = list("pin_testing", "tele_shield", "lasercarbine")
|
||||
design_ids = list("pin_testing", "tele_shield", "lasercarbine", "pin_away")
|
||||
research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 7500)
|
||||
export_price = 5000
|
||||
|
||||
|
||||
@@ -1,47 +1,60 @@
|
||||
/world/TgsNew(datum/tgs_event_handler/event_handler)
|
||||
var/tgs_version = world.params[TGS_VERSION_PARAMETER]
|
||||
if(!tgs_version)
|
||||
/world/TgsNew(datum/tgs_event_handler/event_handler, minimum_required_security_level = TGS_SECURITY_ULTRASAFE)
|
||||
var/current_api = TGS_READ_GLOBAL(tgs)
|
||||
if(current_api)
|
||||
TGS_ERROR_LOG("TgsNew(): TGS API datum already set ([current_api])! Was TgsNew() called more than once?")
|
||||
return
|
||||
|
||||
var/path = SelectTgsApi(tgs_version)
|
||||
if(!path)
|
||||
TGS_ERROR_LOG("Found unsupported API version: [tgs_version]. If this is a valid version please report this, backporting is done on demand.")
|
||||
#ifdef TGS_V3_API
|
||||
minimum_required_security_level = TGS_SECURITY_TRUSTED
|
||||
#endif
|
||||
var/raw_parameter = world.params[TGS_VERSION_PARAMETER]
|
||||
if(!raw_parameter)
|
||||
return
|
||||
|
||||
TGS_INFO_LOG("Activating API for version [tgs_version]")
|
||||
var/datum/tgs_api/new_api = new path
|
||||
var/datum/tgs_version/version = new(raw_parameter)
|
||||
if(!version.Valid(FALSE))
|
||||
TGS_ERROR_LOG("Failed to validate TGS version parameter: [raw_parameter]!")
|
||||
return
|
||||
|
||||
var/result = new_api.OnWorldNew(event_handler ? event_handler : new /datum/tgs_event_handler/tgs_default)
|
||||
if(result && result != TGS_UNIMPLEMENTED)
|
||||
TGS_WRITE_GLOBAL(tgs, new_api)
|
||||
else
|
||||
var/api_datum
|
||||
switch(version.suite)
|
||||
if(3)
|
||||
#ifndef TGS_V3_API
|
||||
TGS_ERROR_LOG("Detected V3 API but TGS_V3_API isn't defined!")
|
||||
#else
|
||||
switch(version.major)
|
||||
if(2)
|
||||
api_datum = /datum/tgs_api/v3210
|
||||
#endif
|
||||
if(4)
|
||||
switch(version.major)
|
||||
if(0)
|
||||
api_datum = /datum/tgs_api/v4
|
||||
|
||||
var/datum/tgs_version/max_api_version = TgsMaximumAPIVersion();
|
||||
if(version.suite != null && version.major != null && version.minor != null && version.patch != null && version.deprefixed_parameter > max_api_version.deprefixed_parameter)
|
||||
TGS_ERROR_LOG("Detected unknown API version! Defaulting to latest. Update the DMAPI to fix this problem.")
|
||||
api_datum = /datum/tgs_api/latest
|
||||
|
||||
if(!api_datum)
|
||||
TGS_ERROR_LOG("Found unsupported API version: [raw_parameter]. If this is a valid version please report this, backporting is done on demand.")
|
||||
return
|
||||
|
||||
TGS_INFO_LOG("Activating API for version [version.deprefixed_parameter]")
|
||||
var/datum/tgs_api/new_api = new api_datum(version)
|
||||
|
||||
TGS_WRITE_GLOBAL(tgs, new_api)
|
||||
|
||||
var/result = new_api.OnWorldNew(event_handler, minimum_required_security_level)
|
||||
if(!result || result == TGS_UNIMPLEMENTED)
|
||||
TGS_WRITE_GLOBAL(tgs, null)
|
||||
TGS_ERROR_LOG("Failed to activate API!")
|
||||
|
||||
/world/proc/SelectTgsApi(tgs_version)
|
||||
//remove the old 3.0 header
|
||||
tgs_version = replacetext(tgs_version, "/tg/station 13 Server v", "")
|
||||
|
||||
var/list/version_bits = splittext(tgs_version, ".")
|
||||
|
||||
var/super = text2num(version_bits[1])
|
||||
var/major = text2num(version_bits[2])
|
||||
var/minor = text2num(version_bits[3])
|
||||
var/patch = text2num(version_bits[4])
|
||||
|
||||
switch(super)
|
||||
if(3)
|
||||
switch(major)
|
||||
if(2)
|
||||
return /datum/tgs_api/v3210
|
||||
|
||||
if(super != null && major != null && minor != null && patch != null && tgs_version > TgsMaximumAPIVersion())
|
||||
TGS_ERROR_LOG("Detected unknown API version! Defaulting to latest. Update the DMAPI to fix this problem.")
|
||||
return /datum/tgs_api/latest
|
||||
|
||||
/world/TgsMaximumAPIVersion()
|
||||
return "4.0.0.0"
|
||||
return new /datum/tgs_version("4.0.x.x")
|
||||
|
||||
/world/TgsMinimumAPIVersion()
|
||||
return "3.2.0.0"
|
||||
return new /datum/tgs_version("3.2.0.0")
|
||||
|
||||
/world/TgsInitializationComplete()
|
||||
var/datum/tgs_api/api = TGS_READ_GLOBAL(tgs)
|
||||
@@ -71,7 +84,9 @@
|
||||
return TGS_READ_GLOBAL(tgs) != null
|
||||
|
||||
/world/TgsVersion()
|
||||
return world.params[TGS_VERSION_PARAMETER]
|
||||
var/datum/tgs_api/api = TGS_READ_GLOBAL(tgs)
|
||||
if(api)
|
||||
return api.version
|
||||
|
||||
/world/TgsInstanceName()
|
||||
var/datum/tgs_api/api = TGS_READ_GLOBAL(tgs)
|
||||
@@ -116,6 +131,11 @@
|
||||
if(api)
|
||||
api.ChatPrivateMessage(message, user)
|
||||
|
||||
/world/TgsSecurityLevel()
|
||||
var/datum/tgs_api/api = TGS_READ_GLOBAL(tgs)
|
||||
if(api)
|
||||
api.SecurityLevel()
|
||||
|
||||
/*
|
||||
The MIT License
|
||||
|
||||
|
||||
@@ -1,9 +1,14 @@
|
||||
TGS_DEFINE_AND_SET_GLOBAL(tgs, null)
|
||||
|
||||
/datum/tgs_api
|
||||
var/datum/tgs_version/version
|
||||
|
||||
/datum/tgs_api/New(datum/tgs_version/version)
|
||||
. = ..()
|
||||
src.version = version
|
||||
|
||||
/datum/tgs_api/latest
|
||||
parent_type = /datum/tgs_api/v3210
|
||||
parent_type = /datum/tgs_api/v4
|
||||
|
||||
TGS_PROTECT_DATUM(/datum/tgs_api)
|
||||
|
||||
@@ -46,6 +51,9 @@ TGS_PROTECT_DATUM(/datum/tgs_api)
|
||||
/datum/tgs_api/proc/ChatPrivateMessage(message, admin_only)
|
||||
return TGS_UNIMPLEMENTED
|
||||
|
||||
/datum/tgs_api/proc/SecurityLevel()
|
||||
return TGS_UNIMPLEMENTED
|
||||
|
||||
/*
|
||||
The MIT License
|
||||
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
/datum/tgs_event_handler/tgs_default/HandleEvent(event_code)
|
||||
//TODO
|
||||
return
|
||||
|
||||
/*
|
||||
The MIT License
|
||||
|
||||
Copyright (c) 2017 Jordan Brown
|
||||
|
||||
Permission is hereby granted, free of charge,
|
||||
to any person obtaining a copy of this software and
|
||||
associated documentation files (the "Software"), to
|
||||
deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify,
|
||||
merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom
|
||||
the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice
|
||||
shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
|
||||
ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
@@ -0,0 +1,22 @@
|
||||
/datum/tgs_version/New(raw_parameter)
|
||||
src.raw_parameter = raw_parameter
|
||||
deprefixed_parameter = replacetext(raw_parameter, "/tg/station 13 Server v", "")
|
||||
var/list/version_bits = splittext(deprefixed_parameter, ".")
|
||||
|
||||
suite = text2num(version_bits[1])
|
||||
if(version_bits.len > 1)
|
||||
major = text2num(version_bits[2])
|
||||
if(version_bits.len > 2)
|
||||
minor = text2num(version_bits[3])
|
||||
if(version_bits.len == 4)
|
||||
patch = text2num(version_bits[4])
|
||||
|
||||
/datum/tgs_version/proc/Valid(allow_wildcards = FALSE)
|
||||
if(suite == null)
|
||||
return FALSE
|
||||
if(allow_wildcards)
|
||||
return TRUE
|
||||
return !Wildcard()
|
||||
|
||||
/datum/tgs_version/Wildcard()
|
||||
return major == null || minor == null || patch == null
|
||||
@@ -1,6 +1,10 @@
|
||||
#include "core\_definitions.dm"
|
||||
#include "core\core.dm"
|
||||
#include "core\datum.dm"
|
||||
#include "core\default_event_handler.dm"
|
||||
#include "core\tgs_version.dm"
|
||||
#ifdef TGS_V3_API
|
||||
#include "v3210\api.dm"
|
||||
#include "v3210\commands.dm"
|
||||
#endif
|
||||
#include "v4\api.dm"
|
||||
#include "v4\commands.dm"
|
||||
|
||||
@@ -56,8 +56,9 @@
|
||||
/datum/tgs_api/v3210/proc/file2list(filename)
|
||||
return splittext(trim_left(trim_right(file2text(filename))), "\n")
|
||||
|
||||
/datum/tgs_api/v3210/OnWorldNew(datum/tgs_event_handler/event_handler) //don't use event handling in this version
|
||||
/datum/tgs_api/v3210/OnWorldNew(datum/tgs_event_handler/event_handler, minimum_required_security_level) //don't use event handling in this version
|
||||
. = FALSE
|
||||
|
||||
comms_key = world.params[SERVICE_WORLD_PARAM]
|
||||
instance_name = world.params[SERVICE_INSTANCE_PARAM]
|
||||
if(!instance_name)
|
||||
@@ -170,6 +171,7 @@
|
||||
var/datum/tgs_revision_information/ri = new
|
||||
ri.commit = commit
|
||||
ri.origin_commit = originmastercommit
|
||||
return ri
|
||||
|
||||
/datum/tgs_api/v3210/EndProcess()
|
||||
sleep(world.tick_lag) //flush the buffers
|
||||
@@ -187,9 +189,12 @@
|
||||
/datum/tgs_api/v3210/ChatTargetedBroadcast(message, admin_only)
|
||||
ExportService("[admin_only ? SERVICE_REQUEST_IRC_ADMIN_CHANNEL_MESSAGE : SERVICE_REQUEST_IRC_BROADCAST] [message]")
|
||||
|
||||
/datum/tgs_api/v3210/ChatPrivateMessage(message, admin_only)
|
||||
/datum/tgs_api/v3210/ChatPrivateMessage(message, datum/tgs_chat_user/user)
|
||||
return TGS_UNIMPLEMENTED
|
||||
|
||||
/datum/tgs_api/v3210/SecurityLevel()
|
||||
return TGS_SECURITY_TRUSTED
|
||||
|
||||
#undef REBOOT_MODE_NORMAL
|
||||
#undef REBOOT_MODE_HARD
|
||||
#undef REBOOT_MODE_SHUTDOWN
|
||||
|
||||
@@ -0,0 +1,342 @@
|
||||
#define TGS4_PARAM_INFO_JSON "tgs_json"
|
||||
|
||||
#define TGS4_INTEROP_ACCESS_IDENTIFIER "tgs_tok"
|
||||
|
||||
#define TGS4_RESPONSE_SUCCESS "tgs_succ"
|
||||
|
||||
#define TGS4_TOPIC_CHANGE_PORT "tgs_port"
|
||||
#define TGS4_TOPIC_CHANGE_REBOOT_MODE "tgs_rmode"
|
||||
#define TGS4_TOPIC_CHAT_COMMAND "tgs_chat_comm"
|
||||
#define TGS4_TOPIC_EVENT "tgs_event"
|
||||
#define TGS4_TOPIC_INTEROP_RESPONSE "tgs_interop"
|
||||
|
||||
#define TGS4_COMM_NEW_PORT "tgs_new_port"
|
||||
#define TGS4_COMM_VALIDATE "tgs_validate"
|
||||
#define TGS4_COMM_SERVER_PRIMED "tgs_prime"
|
||||
#define TGS4_COMM_WORLD_REBOOT "tgs_reboot"
|
||||
#define TGS4_COMM_END_PROCESS "tgs_kill"
|
||||
#define TGS4_COMM_CHAT "tgs_chat_send"
|
||||
|
||||
#define TGS4_PARAMETER_COMMAND "tgs_com"
|
||||
#define TGS4_PARAMETER_DATA "tgs_data"
|
||||
|
||||
#define TGS4_PORT_CRITFAIL_MESSAGE " Must exit to let watchdog reboot..."
|
||||
|
||||
#define EXPORT_TIMEOUT_DS 200
|
||||
|
||||
/datum/tgs_api/v4
|
||||
var/access_identifier
|
||||
var/instance_name
|
||||
var/json_path
|
||||
var/chat_channels_json_path
|
||||
var/chat_commands_json_path
|
||||
var/server_commands_json_path
|
||||
var/reboot_mode = TGS_REBOOT_MODE_NORMAL
|
||||
var/security_level
|
||||
|
||||
var/requesting_new_port = FALSE
|
||||
|
||||
var/list/intercepted_message_queue
|
||||
|
||||
var/list/custom_commands
|
||||
|
||||
var/list/cached_test_merges
|
||||
var/datum/tgs_revision_information/cached_revision
|
||||
|
||||
var/datum/tgs_event_handler/event_handler
|
||||
|
||||
var/export_lock = FALSE
|
||||
var/list/last_interop_response
|
||||
|
||||
/datum/tgs_api/v4/ApiVersion()
|
||||
return "4.0.0.0"
|
||||
|
||||
/datum/tgs_api/v4/OnWorldNew(datum/tgs_event_handler/event_handler, minimum_required_security_level)
|
||||
json_path = world.params[TGS4_PARAM_INFO_JSON]
|
||||
if(!json_path)
|
||||
TGS_ERROR_LOG("Missing [TGS4_PARAM_INFO_JSON] world parameter!")
|
||||
return
|
||||
var/json_file = file2text(json_path)
|
||||
if(!json_file)
|
||||
TGS_ERROR_LOG("Missing specified json file: [json_path]")
|
||||
return
|
||||
var/cached_json = json_decode(json_file)
|
||||
if(!cached_json)
|
||||
TGS_ERROR_LOG("Failed to decode info json: [json_file]")
|
||||
return
|
||||
|
||||
access_identifier = cached_json["accessIdentifier"]
|
||||
server_commands_json_path = cached_json["serverCommandsJson"]
|
||||
|
||||
if(cached_json["apiValidateOnly"])
|
||||
TGS_INFO_LOG("Validating API and exiting...")
|
||||
Export(TGS4_COMM_VALIDATE, list(TGS4_PARAMETER_DATA = "[minimum_required_security_level]"))
|
||||
del(world)
|
||||
|
||||
security_level = cached_json["securityLevel"]
|
||||
chat_channels_json_path = cached_json["chatChannelsJson"]
|
||||
chat_commands_json_path = cached_json["chatCommandsJson"]
|
||||
src.event_handler = event_handler
|
||||
instance_name = cached_json["instanceName"]
|
||||
|
||||
ListCustomCommands()
|
||||
|
||||
var/list/revisionData = cached_json["revision"]
|
||||
if(revisionData)
|
||||
cached_revision = new
|
||||
cached_revision.commit = revisionData["commitSha"]
|
||||
cached_revision.origin_commit = revisionData["originCommitSha"]
|
||||
|
||||
cached_test_merges = list()
|
||||
var/list/json = cached_json["testMerges"]
|
||||
for(var/entry in json)
|
||||
var/datum/tgs_revision_information/test_merge/tm = new
|
||||
tm.time_merged = text2num(entry["timeMerged"])
|
||||
|
||||
var/list/revInfo = entry["revision"]
|
||||
if(revInfo)
|
||||
tm.commit = revInfo["commitSha"]
|
||||
tm.origin_commit = revInfo["originCommitSha"]
|
||||
|
||||
tm.title = entry["titleAtMerge"]
|
||||
tm.body = entry["bodyAtMerge"]
|
||||
tm.url = entry["url"]
|
||||
tm.author = entry["author"]
|
||||
tm.number = entry["number"]
|
||||
tm.pull_request_commit = entry["pullRequestRevision"]
|
||||
tm.comment = entry["comment"]
|
||||
|
||||
cached_test_merges += tm
|
||||
|
||||
return TRUE
|
||||
|
||||
/datum/tgs_api/v4/OnInitializationComplete()
|
||||
Export(TGS4_COMM_SERVER_PRIMED)
|
||||
|
||||
var/tgs4_secret_sleep_offline_sauce = 24051994
|
||||
var/old_sleep_offline = world.sleep_offline
|
||||
world.sleep_offline = tgs4_secret_sleep_offline_sauce
|
||||
sleep(1)
|
||||
if(world.sleep_offline == tgs4_secret_sleep_offline_sauce) //if not someone changed it
|
||||
world.sleep_offline = old_sleep_offline
|
||||
|
||||
/datum/tgs_api/v4/OnTopic(T)
|
||||
var/list/params = params2list(T)
|
||||
var/their_sCK = params[TGS4_INTEROP_ACCESS_IDENTIFIER]
|
||||
if(!their_sCK)
|
||||
return FALSE //continue world/Topic
|
||||
|
||||
if(their_sCK != access_identifier)
|
||||
return "Invalid comms key!";
|
||||
|
||||
var/command = params[TGS4_PARAMETER_COMMAND]
|
||||
if(!command)
|
||||
return "No command!"
|
||||
|
||||
. = TGS4_RESPONSE_SUCCESS
|
||||
|
||||
switch(command)
|
||||
if(TGS4_TOPIC_CHAT_COMMAND)
|
||||
var/result = HandleCustomCommand(params[TGS4_PARAMETER_DATA])
|
||||
if(result == null)
|
||||
result = "Error running chat command!"
|
||||
return result
|
||||
if(TGS4_TOPIC_EVENT)
|
||||
intercepted_message_queue = list()
|
||||
var/list/event_notification = json_decode(params[TGS4_PARAMETER_DATA])
|
||||
var/list/event_parameters = event_notification["Parameters"]
|
||||
|
||||
var/list/event_call = list(event_notification["Type"])
|
||||
if(event_parameters)
|
||||
event_call += event_parameters
|
||||
|
||||
if(event_handler != null)
|
||||
event_handler.HandleEvent(arglist(event_call))
|
||||
|
||||
. = json_encode(intercepted_message_queue)
|
||||
intercepted_message_queue = null
|
||||
return
|
||||
if(TGS4_TOPIC_INTEROP_RESPONSE)
|
||||
last_interop_response = json_decode(params[TGS4_PARAMETER_DATA])
|
||||
return
|
||||
if(TGS4_TOPIC_CHANGE_PORT)
|
||||
var/new_port = text2num(params[TGS4_PARAMETER_DATA])
|
||||
if (!(new_port > 0))
|
||||
return "Invalid port: [new_port]"
|
||||
|
||||
//the topic still completes, miraculously
|
||||
//I honestly didn't believe byond could do it
|
||||
if(event_handler != null)
|
||||
event_handler.HandleEvent(TGS_EVENT_PORT_SWAP, new_port)
|
||||
if(!world.OpenPort(new_port))
|
||||
return "Port change failed!"
|
||||
return
|
||||
if(TGS4_TOPIC_CHANGE_REBOOT_MODE)
|
||||
var/new_reboot_mode = text2num(params[TGS4_PARAMETER_DATA])
|
||||
if(event_handler != null)
|
||||
event_handler.HandleEvent(TGS_EVENT_REBOOT_MODE_CHANGE, reboot_mode, new_reboot_mode)
|
||||
reboot_mode = new_reboot_mode
|
||||
return
|
||||
|
||||
return "Unknown command: [command]"
|
||||
|
||||
/datum/tgs_api/v4/proc/Export(command, list/data, override_requesting_new_port = FALSE)
|
||||
if(!data)
|
||||
data = list()
|
||||
data[TGS4_PARAMETER_COMMAND] = command
|
||||
var/json = json_encode(data)
|
||||
|
||||
while(requesting_new_port && !override_requesting_new_port)
|
||||
sleep(1)
|
||||
|
||||
//we need some port open at this point to facilitate return communication
|
||||
if(!world.port)
|
||||
requesting_new_port = TRUE
|
||||
if(!world.OpenPort(0)) //open any port
|
||||
TGS_ERROR_LOG("Unable to open random port to retrieve new port![TGS4_PORT_CRITFAIL_MESSAGE]")
|
||||
del(world)
|
||||
|
||||
//request a new port
|
||||
export_lock = FALSE
|
||||
var/list/new_port_json = Export(TGS4_COMM_NEW_PORT, list(TGS4_PARAMETER_DATA = "[world.port]"), TRUE) //stringify this on purpose
|
||||
|
||||
if(!new_port_json)
|
||||
TGS_ERROR_LOG("No new port response from server![TGS4_PORT_CRITFAIL_MESSAGE]")
|
||||
del(world)
|
||||
|
||||
var/new_port = new_port_json[TGS4_PARAMETER_DATA]
|
||||
if(!isnum(new_port) || new_port <= 0)
|
||||
TGS_ERROR_LOG("Malformed new port json ([json_encode(new_port_json)])![TGS4_PORT_CRITFAIL_MESSAGE]")
|
||||
del(world)
|
||||
|
||||
if(new_port != world.port && !world.OpenPort(new_port))
|
||||
TGS_ERROR_LOG("Unable to open port [new_port]![TGS4_PORT_CRITFAIL_MESSAGE]")
|
||||
del(world)
|
||||
requesting_new_port = FALSE
|
||||
|
||||
while(export_lock)
|
||||
sleep(1)
|
||||
export_lock = TRUE
|
||||
|
||||
last_interop_response = null
|
||||
fdel(server_commands_json_path)
|
||||
text2file(json, server_commands_json_path)
|
||||
|
||||
for(var/I = 0; I < EXPORT_TIMEOUT_DS && !last_interop_response; ++I)
|
||||
sleep(1)
|
||||
|
||||
if(!last_interop_response)
|
||||
TGS_ERROR_LOG("Failed to get export result for: [json]")
|
||||
else
|
||||
. = last_interop_response
|
||||
|
||||
export_lock = FALSE
|
||||
|
||||
/datum/tgs_api/v4/OnReboot()
|
||||
var/list/result = Export(TGS4_COMM_WORLD_REBOOT)
|
||||
if(!result)
|
||||
return
|
||||
|
||||
//okay so the standard TGS4 proceedure is: right before rebooting change the port to whatever was sent to us in the above json's data parameter
|
||||
|
||||
var/port = result[TGS4_PARAMETER_DATA]
|
||||
if(!isnum(port))
|
||||
return //this is valid, server may just want use to reboot
|
||||
|
||||
if(port == 0)
|
||||
//to byond 0 means any port and "none" means close vOv
|
||||
port = "none"
|
||||
|
||||
if(!world.OpenPort(port))
|
||||
TGS_ERROR_LOG("Unable to set port to [port]!")
|
||||
|
||||
/datum/tgs_api/v4/InstanceName()
|
||||
return instance_name
|
||||
|
||||
/datum/tgs_api/v4/TestMerges()
|
||||
return cached_test_merges
|
||||
|
||||
/datum/tgs_api/v4/EndProcess()
|
||||
Export(TGS4_COMM_END_PROCESS)
|
||||
|
||||
/datum/tgs_api/v4/Revision()
|
||||
return cached_revision
|
||||
|
||||
/datum/tgs_api/v4/ChatBroadcast(message, list/channels)
|
||||
var/list/ids
|
||||
if(length(channels))
|
||||
ids = list()
|
||||
for(var/I in channels)
|
||||
var/datum/tgs_chat_channel/channel = I
|
||||
ids += channel.id
|
||||
message = list("message" = message, "channelIds" = ids)
|
||||
if(intercepted_message_queue)
|
||||
intercepted_message_queue += list(message)
|
||||
else
|
||||
Export(TGS4_COMM_CHAT, message)
|
||||
|
||||
/datum/tgs_api/v4/ChatTargetedBroadcast(message, admin_only)
|
||||
var/list/channels = list()
|
||||
for(var/I in ChatChannelInfo())
|
||||
var/datum/tgs_chat_channel/channel = I
|
||||
if (!channel.is_private_channel && ((channel.is_admin_channel && admin_only) || (!channel.is_admin_channel && !admin_only)))
|
||||
channels += channel.id
|
||||
message = list("message" = message, "channelIds" = channels)
|
||||
if(intercepted_message_queue)
|
||||
intercepted_message_queue += list(message)
|
||||
else
|
||||
Export(TGS4_COMM_CHAT, message)
|
||||
|
||||
/datum/tgs_api/v4/ChatPrivateMessage(message, datum/tgs_chat_user/user)
|
||||
message = list("message" = message, "channelIds" = list(user.channel.id))
|
||||
if(intercepted_message_queue)
|
||||
intercepted_message_queue += list(message)
|
||||
else
|
||||
Export(TGS4_COMM_CHAT, message)
|
||||
|
||||
/datum/tgs_api/v4/ChatChannelInfo()
|
||||
. = list()
|
||||
//no caching cause tgs may change this
|
||||
var/list/json = json_decode(file2text(chat_channels_json_path))
|
||||
for(var/I in json)
|
||||
. += DecodeChannel(I)
|
||||
|
||||
/datum/tgs_api/v4/proc/DecodeChannel(channel_json)
|
||||
var/datum/tgs_chat_channel/channel = new
|
||||
channel.id = channel_json["id"]
|
||||
channel.friendly_name = channel_json["friendlyName"]
|
||||
channel.connection_name = channel_json["connectionName"]
|
||||
channel.is_admin_channel = channel_json["isAdminChannel"]
|
||||
channel.is_private_channel = channel_json["isPrivateChannel"]
|
||||
channel.custom_tag = channel_json["tag"]
|
||||
return channel
|
||||
|
||||
/datum/tgs_api/v4/SecurityLevel()
|
||||
return security_level
|
||||
|
||||
/*
|
||||
The MIT License
|
||||
|
||||
Copyright (c) 2017 Jordan Brown
|
||||
|
||||
Permission is hereby granted, free of charge,
|
||||
to any person obtaining a copy of this software and
|
||||
associated documentation files (the "Software"), to
|
||||
deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify,
|
||||
merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom
|
||||
the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice
|
||||
shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
|
||||
ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
@@ -0,0 +1,69 @@
|
||||
/datum/tgs_api/v4/proc/ListCustomCommands()
|
||||
var/results = list()
|
||||
custom_commands = list()
|
||||
for(var/I in typesof(/datum/tgs_chat_command) - /datum/tgs_chat_command)
|
||||
var/datum/tgs_chat_command/stc = new I
|
||||
var/command_name = stc.name
|
||||
if(!command_name || findtext(command_name, " ") || findtext(command_name, "'") || findtext(command_name, "\""))
|
||||
TGS_ERROR_LOG("Custom command [command_name] ([I]) can't be used as it is empty or contains illegal characters!")
|
||||
continue
|
||||
|
||||
if(results[command_name])
|
||||
var/datum/other = custom_commands[command_name]
|
||||
TGS_ERROR_LOG("Custom commands [other.type] and [I] have the same name (\"[command_name]\"), only [other.type] will be available!")
|
||||
continue
|
||||
results += list(list("name" = command_name, "help_text" = stc.help_text, "admin_only" = stc.admin_only))
|
||||
custom_commands[command_name] = stc
|
||||
|
||||
var/commands_file = chat_commands_json_path
|
||||
if(!commands_file)
|
||||
return
|
||||
text2file(json_encode(results), commands_file)
|
||||
|
||||
/datum/tgs_api/v4/proc/HandleCustomCommand(command_json)
|
||||
var/list/data = json_decode(command_json)
|
||||
var/command = data["command"]
|
||||
var/user = data["user"]
|
||||
var/params = data["params"]
|
||||
|
||||
var/datum/tgs_chat_user/u = new
|
||||
u.id = user["id"]
|
||||
u.friendly_name = user["friendlyName"]
|
||||
u.mention = user["mention"]
|
||||
u.channel = DecodeChannel(user["channel"])
|
||||
|
||||
var/datum/tgs_chat_command/sc = custom_commands[command]
|
||||
if(sc)
|
||||
var/result = sc.Run(u, params)
|
||||
if(result == null)
|
||||
result = ""
|
||||
return result
|
||||
return "Unknown command: [command]!"
|
||||
|
||||
/*
|
||||
|
||||
The MIT License
|
||||
|
||||
Copyright (c) 2017 Jordan Brown
|
||||
|
||||
Permission is hereby granted, free of charge,
|
||||
to any person obtaining a copy of this software and
|
||||
associated documentation files (the "Software"), to
|
||||
deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify,
|
||||
merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom
|
||||
the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice
|
||||
shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
|
||||
ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
@@ -399,3 +399,35 @@
|
||||
|
||||
/obj/item/vending_refill/wardrobe/viro_wardrobe
|
||||
machine_name = "ViroDrobe"
|
||||
|
||||
/obj/machinery/vending/wardrobe/cap_wardrobe
|
||||
name = "Captain's Wardrobe"
|
||||
desc = "The latest and greatest in Nanotrasen fashion for your great leader."
|
||||
icon_state = "capsdrobe"
|
||||
icon_deny = "capsdrobe-deny"
|
||||
product_ads = "Only the greatest for a commander such as ours."
|
||||
req_access = list(ACCESS_CAPTAIN)
|
||||
vend_reply = "A wonderful day to you, great leader."
|
||||
products = list(/obj/item/clothing/suit/hooded/wintercoat/captain = 1,
|
||||
/obj/item/storage/backpack/captain = 1,
|
||||
/obj/item/storage/backpack/satchel/cap = 1,
|
||||
/obj/item/storage/backpack/duffelbag/captain = 1,
|
||||
/obj/item/clothing/neck/cloak/cap = 1,
|
||||
/obj/item/clothing/shoes/sneakers/brown = 2,
|
||||
/obj/item/clothing/under/rank/captain = 1,
|
||||
/obj/item/clothing/under/rank/captain/skirt = 1,
|
||||
/obj/item/clothing/head/caphat = 1,
|
||||
/obj/item/clothing/head/caphat/parade = 1,
|
||||
/obj/item/clothing/head/caphat/beret = 1,
|
||||
/obj/item/clothing/head/caphat/beret/white = 1,
|
||||
/obj/item/clothing/under/captainparade = 1,
|
||||
/obj/item/clothing/suit/captunic = 1,
|
||||
/obj/item/clothing/under/rank/captain/femformal = 1,
|
||||
/obj/item/clothing/glasses/sunglasses/gar/supergar = 1,
|
||||
/obj/item/clothing/gloves/color/captain = 1)
|
||||
refill_canister = /obj/item/vending_refill/wardrobe/cap_wardrobe
|
||||
|
||||
/obj/item/vending_refill/wardrobe/cap_wardrobe
|
||||
machine_name = "Captain's Wardrobe"
|
||||
icon_state = "refill_caps"
|
||||
|
||||
@@ -448,8 +448,14 @@ MINUTE_CLICK_LIMIT 400
|
||||
##How long to wait between messaging admins about occurences of a unique error
|
||||
#ERROR_MSG_DELAY 50
|
||||
|
||||
## Send a message to IRC when starting a new game
|
||||
#IRC_ANNOUNCE_NEW_GAME
|
||||
## Chat Announce Options
|
||||
## Various messages to be sent to game chats
|
||||
## Uncommenting these will enable them, by default they will be broadcast to Game chat channels on TGS3 or non-admin channels on TGS4
|
||||
## If using TGS4, the string option can be set as a chat channel tag to limit the message to channels of that tag type (case-sensitive)
|
||||
## i.e. CHAT_ANNOUNCE_NEW_GAME chat_channel_tag
|
||||
|
||||
## Send a message with the station name starting a new game
|
||||
#CHAT_ANNOUNCE_NEW_GAME
|
||||
|
||||
## Allow admin hrefs that don't use the new token system, will eventually be removed
|
||||
DEBUG_ADMIN_HREFS
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
author: "Ghommie"
|
||||
delete-after: True
|
||||
changes:
|
||||
- bugfix: "Fixed a little inconvenience with the character setup preview dummy having extra unwarranted bits."
|
||||
@@ -0,0 +1,4 @@
|
||||
author: "kevinz000"
|
||||
delete-after: True
|
||||
changes:
|
||||
- rscadd: "gravity gun repulse and chaos now work in all angles, rather than just basic cardinals and diagonals. fun."
|
||||
@@ -0,0 +1,4 @@
|
||||
author: "Seris02"
|
||||
delete-after: True
|
||||
changes:
|
||||
- rscadd: "crowbarring manifests off crates"
|
||||
@@ -0,0 +1,4 @@
|
||||
author: "Trilbyspaceclone"
|
||||
delete-after: True
|
||||
changes:
|
||||
- rscadd: "Firing pins that only works when not on station"
|
||||
@@ -0,0 +1,4 @@
|
||||
author: "Ghommie"
|
||||
delete-after: True
|
||||
changes:
|
||||
- imageadd: "Fixed dozen missing privates sprites."
|
||||
@@ -0,0 +1,4 @@
|
||||
author: "Arturlang"
|
||||
delete-after: True
|
||||
changes:
|
||||
- tweak: "Hyposprays now switch modes on CTRL click, instead of alt, as it was already reserved for dispension amount."
|
||||
@@ -0,0 +1,4 @@
|
||||
author: "keronshb"
|
||||
delete-after: True
|
||||
changes:
|
||||
- rscadd: "Adds the CogChamp drink and Sprite"
|
||||
@@ -0,0 +1,4 @@
|
||||
author: "kevinz000"
|
||||
delete-after: True
|
||||
changes:
|
||||
- bugfix: "Brig cells now are more accurate by using timeofday instead of realtime"
|
||||
@@ -0,0 +1,5 @@
|
||||
author: "Trilbyspaceclone"
|
||||
delete-after: True
|
||||
changes:
|
||||
- balance: "The suit full of spiders also known as a ninja now is a tactical turtleneck"
|
||||
- balance: "The Wiznerds now dont have suits set to max"
|
||||
@@ -0,0 +1,5 @@
|
||||
author: "ShadeAware"
|
||||
delete-after: True
|
||||
changes:
|
||||
- rscadd: "The Captain's Wardrobe, a special one-of-a-kind and ID-locked wardrobe for the Captain that holds all of their snowflakey gear.
|
||||
Remove: Snowflake gear from the Captain's locker, since now it has its own vendor."
|
||||
|
Before Width: | Height: | Size: 89 KiB After Width: | Height: | Size: 90 KiB |
|
Before Width: | Height: | Size: 104 KiB After Width: | Height: | Size: 106 KiB |
|
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 2.3 KiB |
@@ -10,12 +10,6 @@
|
||||
var/hidden_undershirt = FALSE
|
||||
var/hidden_socks = FALSE
|
||||
|
||||
//Species vars
|
||||
/datum/species
|
||||
var/list/cum_fluids = list("semen")
|
||||
var/list/milk_fluids = list("milk")
|
||||
var/list/femcum_fluids = list("femcum")
|
||||
|
||||
//Mob procs
|
||||
/mob/living/carbon/human/proc/underwear_toggle()
|
||||
set name = "Toggle undergarments"
|
||||
@@ -46,20 +40,12 @@
|
||||
var/list/obj/item/organ/genital/genit_list = list()
|
||||
if(!client?.prefs.arousable || (aphro && (client?.prefs.cit_toggles & NO_APHRO)) || (maso && !HAS_TRAIT(src, TRAIT_MASO)))
|
||||
return // no adjusting made here
|
||||
if(strength>0)
|
||||
for(var/obj/item/organ/genital/G in internal_organs)
|
||||
if(!G.aroused_state && prob(strength*G.sensitivity))
|
||||
G.set_aroused_state(TRUE)
|
||||
G.update_appearance()
|
||||
if(G.aroused_state)
|
||||
genit_list += G
|
||||
else
|
||||
for(var/obj/item/organ/genital/G in internal_organs)
|
||||
if(G.aroused_state && prob(strength*G.sensitivity))
|
||||
G.set_aroused_state(FALSE)
|
||||
G.update_appearance()
|
||||
if(G.aroused_state)
|
||||
genit_list += G
|
||||
for(var/obj/item/organ/genital/G in internal_organs)
|
||||
if(G.genital_flags & GENITAL_CAN_AROUSE && !G.aroused_state && prob(strength*G.sensitivity))
|
||||
G.set_aroused_state(strength > 0)
|
||||
G.update_appearance()
|
||||
if(G.aroused_state)
|
||||
genit_list += G
|
||||
return genit_list
|
||||
|
||||
/obj/item/organ/genital/proc/climaxable(mob/living/carbon/human/H, silent = FALSE) //returns the fluid source (ergo reagents holder) if found.
|
||||
|
||||
@@ -32,23 +32,20 @@
|
||||
else
|
||||
update()
|
||||
|
||||
/obj/item/organ/genital/Destroy()
|
||||
if(linked_organ)
|
||||
update_link(TRUE)//this should remove any other links it has
|
||||
return ..()
|
||||
|
||||
/obj/item/organ/genital/proc/set_aroused_state(new_state)
|
||||
if(!(genital_flags & GENITAL_CAN_AROUSE))
|
||||
return FALSE
|
||||
if(!((HAS_TRAIT(owner,TRAIT_PERMABONER) && !new_state) || HAS_TRAIT(owner,TRAIT_NEVERBONER) && new_state))
|
||||
aroused_state = new_state
|
||||
return aroused_state
|
||||
|
||||
/obj/item/organ/genital/proc/update(removing = FALSE)
|
||||
/obj/item/organ/genital/proc/update()
|
||||
if(QDELETED(src))
|
||||
return
|
||||
update_size()
|
||||
update_appearance()
|
||||
if(linked_organ_slot || (linked_organ && removing))
|
||||
update_link(removing)
|
||||
if(linked_organ_slot || (linked_organ && !owner))
|
||||
update_link()
|
||||
|
||||
//exposure and through-clothing code
|
||||
/mob/living/carbon
|
||||
@@ -113,17 +110,7 @@
|
||||
set desc = "Allows you to toggle which genitals are showing signs of arousal."
|
||||
var/list/genital_list = list()
|
||||
for(var/obj/item/organ/genital/G in internal_organs)
|
||||
var/datum/sprite_accessory/S
|
||||
switch(G.type)
|
||||
if(/obj/item/organ/genital/penis)
|
||||
S = GLOB.cock_shapes_list[G.shape]
|
||||
if(/obj/item/organ/genital/testicles)
|
||||
S = GLOB.balls_shapes_list[G.shape]
|
||||
if(/obj/item/organ/genital/vagina)
|
||||
S = GLOB.vagina_shapes_list[G.shape]
|
||||
if(/obj/item/organ/genital/breasts)
|
||||
S = GLOB.breasts_shapes_list[G.shape]
|
||||
if(S?.alt_aroused)
|
||||
if(G.genital_flags & GENITAL_CAN_AROUSE)
|
||||
genital_list += G
|
||||
if(!genital_list.len) //There's nothing that can show arousal
|
||||
return
|
||||
@@ -174,19 +161,18 @@
|
||||
return TRUE
|
||||
return FALSE
|
||||
|
||||
/obj/item/organ/genital/proc/update_link(removing = FALSE)
|
||||
if(!removing && owner)
|
||||
/obj/item/organ/genital/proc/update_link()
|
||||
if(owner)
|
||||
if(linked_organ)
|
||||
return
|
||||
return FALSE
|
||||
linked_organ = owner.getorganslot(linked_organ_slot)
|
||||
if(linked_organ)
|
||||
linked_organ.linked_organ = src
|
||||
linked_organ.upon_link()
|
||||
upon_link()
|
||||
return TRUE
|
||||
else
|
||||
if(linked_organ)
|
||||
linked_organ.linked_organ = null
|
||||
if(linked_organ)
|
||||
linked_organ.linked_organ = null
|
||||
linked_organ = null
|
||||
return FALSE
|
||||
|
||||
@@ -200,10 +186,13 @@
|
||||
update()
|
||||
RegisterSignal(owner, COMSIG_MOB_DEATH, .proc/update_appearance)
|
||||
|
||||
/obj/item/organ/genital/Remove(mob/living/carbon/M, special = FALSE, drop_if_replaced = TRUE)
|
||||
update(TRUE)
|
||||
if(!QDELETED(owner))
|
||||
UnregisterSignal(owner, COMSIG_MOB_DEATH)
|
||||
/obj/item/organ/genital/Remove(special = FALSE)
|
||||
. = ..()
|
||||
var/mob/living/carbon/human/H = .
|
||||
update()
|
||||
if(!QDELETED(H))
|
||||
UnregisterSignal(H, COMSIG_MOB_DEATH)
|
||||
H.update_genitals()
|
||||
|
||||
//proc to give a player their genitals and stuff when they log in
|
||||
/mob/living/carbon/human/proc/give_genitals(clean = FALSE)//clean will remove all pre-existing genitals. proc will then give them any genitals that are enabled in their DNA
|
||||
@@ -238,12 +227,6 @@
|
||||
/obj/item/organ/genital/proc/get_features(mob/living/carbon/human/H)
|
||||
return
|
||||
|
||||
/datum/species/proc/genitals_layertext(layer)
|
||||
switch(layer)
|
||||
if(GENITALS_BEHIND_LAYER)
|
||||
return "BEHIND"
|
||||
if(GENITALS_FRONT_LAYER)
|
||||
return "FRONT"
|
||||
|
||||
//procs to handle sprite overlays being applied to humans
|
||||
|
||||
@@ -262,8 +245,90 @@
|
||||
update_genitals()
|
||||
|
||||
/mob/living/carbon/human/proc/update_genitals()
|
||||
if(!QDELETED(src))
|
||||
dna.species.handle_genitals(src)
|
||||
if(QDELETED(src))
|
||||
return
|
||||
var/static/list/relevant_layers = list("[GENITALS_BEHIND_LAYER]" = "BEHIND", "[GENITALS_FRONT_LAYER]" = "FRONT")
|
||||
var/static/list/layers_num
|
||||
if(!layers_num)
|
||||
for(var/L in relevant_layers)
|
||||
LAZYSET(layers_num, L, text2num(L))
|
||||
for(var/L in relevant_layers) //Less hardcode
|
||||
remove_overlay(layers_num[L])
|
||||
remove_overlay(GENITALS_EXPOSED_LAYER)
|
||||
if(!LAZYLEN(internal_organs) || ((NOGENITALS in dna.species.species_traits) && !genital_override) || HAS_TRAIT(src, TRAIT_HUSK))
|
||||
return
|
||||
|
||||
//start scanning for genitals
|
||||
|
||||
var/list/gen_index[GENITAL_LAYER_INDEX_LENGTH]
|
||||
var/list/genitals_to_add
|
||||
var/list/fully_exposed
|
||||
for(var/obj/item/organ/genital/G in internal_organs)
|
||||
if(G.is_exposed()) //Checks appropriate clothing slot and if it's through_clothes
|
||||
LAZYADD(gen_index[G.layer_index], G)
|
||||
for(var/L in gen_index)
|
||||
if(L) //skip nulls
|
||||
LAZYADD(genitals_to_add, L)
|
||||
if(!genitals_to_add)
|
||||
return
|
||||
//Now we added all genitals that aren't internal and should be rendered
|
||||
//start applying overlays
|
||||
for(var/layer in relevant_layers)
|
||||
var/list/standing = list()
|
||||
var/layertext = relevant_layers[layer]
|
||||
for(var/A in genitals_to_add)
|
||||
var/obj/item/organ/genital/G = A
|
||||
var/datum/sprite_accessory/S
|
||||
var/size = G.size
|
||||
switch(G.type)
|
||||
if(/obj/item/organ/genital/penis)
|
||||
S = GLOB.cock_shapes_list[G.shape]
|
||||
if(/obj/item/organ/genital/testicles)
|
||||
S = GLOB.balls_shapes_list[G.shape]
|
||||
if(/obj/item/organ/genital/vagina)
|
||||
S = GLOB.vagina_shapes_list[G.shape]
|
||||
if(/obj/item/organ/genital/breasts)
|
||||
S = GLOB.breasts_shapes_list[G.shape]
|
||||
|
||||
if(!S || S.icon_state == "none")
|
||||
continue
|
||||
var/aroused_state = G.aroused_state && S.alt_aroused
|
||||
|
||||
var/mutable_appearance/genital_overlay = mutable_appearance(S.icon, layer = -layer)
|
||||
if(S.center)
|
||||
genital_overlay = center_image(genital_overlay, S.dimension_x, S.dimension_y)
|
||||
|
||||
if(dna.species.use_skintones && dna.features["genitals_use_skintone"])
|
||||
genital_overlay.color = "#[skintone2hex(skin_tone)]"
|
||||
else
|
||||
switch(S.color_src)
|
||||
if("cock_color")
|
||||
genital_overlay.color = "#[dna.features["cock_color"]]"
|
||||
if("balls_color")
|
||||
genital_overlay.color = "#[dna.features["balls_color"]]"
|
||||
if("breasts_color")
|
||||
genital_overlay.color = "#[dna.features["breasts_color"]]"
|
||||
if("vag_color")
|
||||
genital_overlay.color = "#[dna.features["vag_color"]]"
|
||||
|
||||
genital_overlay.icon_state = "[G.slot]_[S.icon_state]_[size][dna.species.use_skintones ? "_s" : ""]_[aroused_state]_[layertext]"
|
||||
|
||||
if(layer == GENITALS_FRONT_LAYER && CHECK_BITFIELD(G.genital_flags, GENITAL_THROUGH_CLOTHES))
|
||||
genital_overlay.layer = -GENITALS_EXPOSED_LAYER
|
||||
LAZYADD(fully_exposed, genital_overlay) // to be added to a layer with higher priority than clothes, hence the name of the bitflag.
|
||||
else
|
||||
standing += genital_overlay
|
||||
|
||||
if(LAZYLEN(standing))
|
||||
overlays_standing[layers_num[layer]] = standing
|
||||
|
||||
if(LAZYLEN(fully_exposed))
|
||||
overlays_standing[GENITALS_EXPOSED_LAYER] = fully_exposed
|
||||
apply_overlay(GENITALS_EXPOSED_LAYER)
|
||||
|
||||
for(var/L in relevant_layers)
|
||||
apply_overlay(layers_num[L])
|
||||
|
||||
|
||||
//Checks to see if organs are new on the mob, and changes their colours so that they don't get crazy colours.
|
||||
/mob/living/carbon/human/proc/emergent_genital_call()
|
||||
@@ -292,86 +357,3 @@
|
||||
else if (willyCheck == FALSE)
|
||||
dna.features["cock_color"] = dna.features["breasts_color"]
|
||||
return TRUE
|
||||
|
||||
/datum/species/proc/handle_genitals(mob/living/carbon/human/H)//more like handle sadness
|
||||
if(!H)//no args
|
||||
CRASH("H = null")
|
||||
if(!LAZYLEN(H.internal_organs) || ((NOGENITALS in species_traits) && !H.genital_override) || HAS_TRAIT(H, TRAIT_HUSK))
|
||||
return
|
||||
var/list/relevant_layers = list(GENITALS_BEHIND_LAYER, GENITALS_FRONT_LAYER)
|
||||
|
||||
for(var/L in relevant_layers) //Less hardcode
|
||||
H.remove_overlay(L)
|
||||
H.remove_overlay(GENITALS_EXPOSED_LAYER)
|
||||
//start scanning for genitals
|
||||
|
||||
var/list/gen_index[GENITAL_LAYER_INDEX_LENGTH]
|
||||
var/list/genitals_to_add
|
||||
var/list/fully_exposed
|
||||
for(var/obj/item/organ/genital/G in H.internal_organs)
|
||||
if(G.is_exposed()) //Checks appropriate clothing slot and if it's through_clothes
|
||||
LAZYADD(gen_index[G.layer_index], G)
|
||||
for(var/L in gen_index)
|
||||
if(L) //skip nulls
|
||||
LAZYADD(genitals_to_add, L)
|
||||
if(!genitals_to_add)
|
||||
return
|
||||
//Now we added all genitals that aren't internal and should be rendered
|
||||
//start applying overlays
|
||||
for(var/layer in relevant_layers)
|
||||
var/list/standing = list()
|
||||
var/layertext = genitals_layertext(layer)
|
||||
for(var/A in genitals_to_add)
|
||||
var/obj/item/organ/genital/G = A
|
||||
var/datum/sprite_accessory/S
|
||||
var/size = G.size
|
||||
var/aroused_state = G.aroused_state
|
||||
switch(G.type)
|
||||
if(/obj/item/organ/genital/penis)
|
||||
S = GLOB.cock_shapes_list[G.shape]
|
||||
if(/obj/item/organ/genital/testicles)
|
||||
S = GLOB.balls_shapes_list[G.shape]
|
||||
if(/obj/item/organ/genital/vagina)
|
||||
S = GLOB.vagina_shapes_list[G.shape]
|
||||
if(/obj/item/organ/genital/breasts)
|
||||
S = GLOB.breasts_shapes_list[G.shape]
|
||||
|
||||
if(!S || S.icon_state == "none")
|
||||
continue
|
||||
|
||||
var/mutable_appearance/genital_overlay = mutable_appearance(S.icon, layer = -layer)
|
||||
genital_overlay.icon_state = "[G.slot]_[S.icon_state]_[size]_[aroused_state]_[layertext]"
|
||||
|
||||
if(S.center)
|
||||
genital_overlay = center_image(genital_overlay, S.dimension_x, S.dimension_y)
|
||||
|
||||
if(use_skintones && H.dna.features["genitals_use_skintone"])
|
||||
genital_overlay.color = "#[skintone2hex(H.skin_tone)]"
|
||||
else
|
||||
switch(S.color_src)
|
||||
if("cock_color")
|
||||
genital_overlay.color = "#[H.dna.features["cock_color"]]"
|
||||
if("balls_color")
|
||||
genital_overlay.color = "#[H.dna.features["balls_color"]]"
|
||||
if("breasts_color")
|
||||
genital_overlay.color = "#[H.dna.features["breasts_color"]]"
|
||||
if("vag_color")
|
||||
genital_overlay.color = "#[H.dna.features["vag_color"]]"
|
||||
|
||||
genital_overlay.icon_state = "[G.slot]_[S.icon_state]_[size]-s_[aroused_state]_[layertext]"
|
||||
|
||||
if(layer == GENITALS_FRONT_LAYER && CHECK_BITFIELD(G.genital_flags, GENITAL_THROUGH_CLOTHES))
|
||||
genital_overlay.layer = -GENITALS_EXPOSED_LAYER
|
||||
LAZYADD(fully_exposed, genital_overlay) // to be added to a layer with higher priority than clothes, hence the name of the bitflag.
|
||||
else
|
||||
standing += genital_overlay
|
||||
|
||||
if(LAZYLEN(standing))
|
||||
H.overlays_standing[layer] = standing
|
||||
|
||||
if(LAZYLEN(fully_exposed))
|
||||
H.overlays_standing[GENITALS_EXPOSED_LAYER] = fully_exposed
|
||||
H.apply_overlay(GENITALS_EXPOSED_LAYER)
|
||||
|
||||
for(var/L in relevant_layers)
|
||||
H.apply_overlay(L)
|
||||
|
||||
@@ -85,36 +85,36 @@
|
||||
//Vaginas
|
||||
/datum/sprite_accessory/vagina
|
||||
icon = 'modular_citadel/icons/obj/genitals/vagina_onmob.dmi'
|
||||
icon_state = null
|
||||
name = "vagina"
|
||||
color_src = "vag_color"
|
||||
alt_aroused = TRUE
|
||||
|
||||
/datum/sprite_accessory/vagina/human
|
||||
icon_state = "human"
|
||||
name = "Human"
|
||||
alt_aroused = TRUE
|
||||
|
||||
/datum/sprite_accessory/vagina/tentacles
|
||||
icon_state = "tentacle"
|
||||
name = "Tentacle"
|
||||
alt_aroused = TRUE
|
||||
|
||||
/datum/sprite_accessory/vagina/dentata
|
||||
icon_state = "dentata"
|
||||
name = "Dentata"
|
||||
alt_aroused = TRUE
|
||||
|
||||
/datum/sprite_accessory/vagina/hairy
|
||||
icon_state = "hairy"
|
||||
name = "Hairy"
|
||||
alt_aroused = FALSE
|
||||
|
||||
/datum/sprite_accessory/vagina/spade
|
||||
icon_state = "spade"
|
||||
name = "Spade"
|
||||
alt_aroused = FALSE
|
||||
|
||||
/datum/sprite_accessory/vagina/furred
|
||||
icon_state = "furred"
|
||||
name = "Furred"
|
||||
alt_aroused = FALSE
|
||||
|
||||
/datum/sprite_accessory/vagina/gaping
|
||||
icon_state = "gaping"
|
||||
@@ -125,7 +125,6 @@
|
||||
icon = 'modular_citadel/icons/obj/genitals/breasts_onmob.dmi'
|
||||
name = "breasts"
|
||||
color_src = "breasts_color"
|
||||
alt_aroused = TRUE
|
||||
|
||||
/datum/sprite_accessory/breasts/pair
|
||||
icon_state = "pair"
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
#define BREASTS_ICON_MIN_SIZE 1
|
||||
#define BREASTS_ICON_MAX_SIZE 6
|
||||
|
||||
/obj/item/organ/genital/breasts
|
||||
name = "breasts"
|
||||
desc = "Female milk producing organs."
|
||||
@@ -9,7 +12,7 @@
|
||||
fluid_id = /datum/reagent/consumable/milk
|
||||
fluid_rate = MILK_RATE
|
||||
shape = "pair"
|
||||
genital_flags = CAN_MASTURBATE_WITH|CAN_CLIMAX_WITH|GENITAL_FUID_PRODUCTION
|
||||
genital_flags = CAN_MASTURBATE_WITH|CAN_CLIMAX_WITH|GENITAL_FUID_PRODUCTION|GENITAL_CAN_AROUSE
|
||||
masturbation_verb = "massage"
|
||||
arousal_verb = "Your breasts start feeling sensitive"
|
||||
unarousal_verb = "Your breasts no longer feel sensitive"
|
||||
@@ -49,21 +52,21 @@
|
||||
var/datum/reagent/R = GLOB.chemical_reagents_list[fluid_id]
|
||||
if(R)
|
||||
desc += " They're leaking [lowertext(R.name)]."
|
||||
var/string
|
||||
var/datum/sprite_accessory/S = GLOB.breasts_shapes_list[shape]
|
||||
var/icon_shape = S ? S.icon_state : "pair"
|
||||
var/icon_size = CLAMP(breast_values[size], BREASTS_ICON_MIN_SIZE, BREASTS_ICON_MAX_SIZE)
|
||||
icon_state = "breasts_[icon_shape]_[breast_values[icon_size]]"
|
||||
if(owner)
|
||||
if(owner.dna.species.use_skintones && owner.dna.features["genitals_use_skintone"])
|
||||
if(ishuman(owner)) // Check before recasting type, although someone fucked up if you're not human AND have use_skintones somehow...
|
||||
var/mob/living/carbon/human/H = owner // only human mobs have skin_tone, which we need.
|
||||
color = "#[skintone2hex(H.skin_tone)]"
|
||||
string = "breasts_[GLOB.breasts_shapes_icons[shape]]_[size]-s"
|
||||
icon_state += "_s"
|
||||
else
|
||||
color = "#[owner.dna.features["breasts_color"]]"
|
||||
string = "breasts_[GLOB.breasts_shapes_icons[shape]]_[size]"
|
||||
if(ishuman(owner))
|
||||
var/mob/living/carbon/human/H = owner
|
||||
icon_state = sanitize_text(string)
|
||||
H.update_genitals()
|
||||
icon_state = sanitize_text(string)
|
||||
|
||||
//Allows breasts to grow and change size, with sprite changes too.
|
||||
//maximum wah
|
||||
@@ -130,3 +133,6 @@
|
||||
cached_size = size
|
||||
size = breast_values[size]
|
||||
prev_size = cached_size
|
||||
|
||||
#undef BREASTS_ICON_MIN_SIZE
|
||||
#undef BREASTS_ICON_MAX_SIZE
|
||||
@@ -8,13 +8,13 @@
|
||||
masturbation_verb = "stroke"
|
||||
arousal_verb = "You pop a boner"
|
||||
unarousal_verb = "Your boner goes down"
|
||||
genital_flags = CAN_MASTURBATE_WITH|CAN_CLIMAX_WITH
|
||||
genital_flags = CAN_MASTURBATE_WITH|CAN_CLIMAX_WITH|GENITAL_CAN_AROUSE
|
||||
linked_organ_slot = ORGAN_SLOT_TESTICLES
|
||||
fluid_transfer_factor = 0.5
|
||||
size = 2 //arbitrary value derived from length and girth for sprites.
|
||||
layer_index = PENIS_LAYER_INDEX
|
||||
var/length = 6 //inches
|
||||
var/prev_length = 6 //really should be renamed to prev_length
|
||||
var/prev_length = 6
|
||||
var/girth = 4.38
|
||||
var/girth_ratio = COCK_GIRTH_RATIO_DEF //0.73; check citadel_defines.dm
|
||||
|
||||
@@ -73,22 +73,21 @@
|
||||
|
||||
/obj/item/organ/genital/penis/update_appearance()
|
||||
. = ..()
|
||||
var/string
|
||||
var/datum/sprite_accessory/S = GLOB.cock_shapes_list[shape]
|
||||
var/icon_shape = S ? S.icon_state : "human"
|
||||
icon_state = "penis_[icon_shape]_[size]"
|
||||
var/lowershape = lowertext(shape)
|
||||
desc = "You see [aroused_state ? "an erect" : "a flaccid"] [lowershape] [name]. You estimate it's about [round(length, 0.25)] inch[round(length, 0.25) != 1 ? "es" : ""] long and [round(girth, 0.25)] inch[round(girth, 0.25) != 1 ? "es" : ""] in girth."
|
||||
|
||||
if(owner)
|
||||
if(owner.dna.species.use_skintones && owner.dna.features["genitals_use_skintone"])
|
||||
if(ishuman(owner)) // Check before recasting type, although someone fucked up if you're not human AND have use_skintones somehow...
|
||||
var/mob/living/carbon/human/H = owner // only human mobs have skin_tone, which we need.
|
||||
color = "#[skintone2hex(H.skin_tone)]"
|
||||
string = "penis_[GLOB.cock_shapes_icons[shape]]_[size]-s"
|
||||
icon_state += "_s"
|
||||
else
|
||||
color = "#[owner.dna.features["cock_color"]]"
|
||||
string = "penis_[GLOB.cock_shapes_icons[shape]]_[size]"
|
||||
if(ishuman(owner))
|
||||
var/mob/living/carbon/human/H = owner
|
||||
icon_state = sanitize_text(string)
|
||||
H.update_genitals()
|
||||
|
||||
/obj/item/organ/genital/penis/get_features(mob/living/carbon/human/H)
|
||||
|
||||
@@ -43,19 +43,19 @@
|
||||
/obj/item/organ/genital/testicles/update_appearance()
|
||||
. = ..()
|
||||
desc = "You see an [size_name] pair of testicles."
|
||||
var/datum/sprite_accessory/S = GLOB.balls_shapes_list[shape]
|
||||
var/icon_shape = S ? S.icon_state : "single"
|
||||
icon_state = "testicles_[icon_shape]_[size]"
|
||||
if(owner)
|
||||
var/string
|
||||
if(owner.dna.species.use_skintones && owner.dna.features["genitals_use_skintone"])
|
||||
if(ishuman(owner)) // Check before recasting type, although someone fucked up if you're not human AND have use_skintones somehow...
|
||||
var/mob/living/carbon/human/H = owner // only human mobs have skin_tone, which we need.
|
||||
color = "#[skintone2hex(H.skin_tone)]"
|
||||
string = "testicles_[GLOB.balls_shapes_icons[shape]]_[size]-s"
|
||||
icon_state += "_s"
|
||||
else
|
||||
color = "#[owner.dna.features["balls_color"]]"
|
||||
string = "testicles_[GLOB.balls_shapes_icons[shape]]_[size]"
|
||||
if(ishuman(owner))
|
||||
var/mob/living/carbon/human/H = owner
|
||||
icon_state = sanitize_text(string)
|
||||
H.update_genitals()
|
||||
|
||||
/obj/item/organ/genital/testicles/get_features(mob/living/carbon/human/H)
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
zone = BODY_ZONE_PRECISE_GROIN
|
||||
slot = "vagina"
|
||||
size = 1 //There is only 1 size right now
|
||||
genital_flags = CAN_MASTURBATE_WITH|CAN_CLIMAX_WITH
|
||||
genital_flags = CAN_MASTURBATE_WITH|CAN_CLIMAX_WITH|GENITAL_CAN_AROUSE
|
||||
masturbation_verb = "finger"
|
||||
arousal_verb = "You feel wetness on your crotch"
|
||||
unarousal_verb = "You no longer feel wet"
|
||||
@@ -22,7 +22,7 @@
|
||||
|
||||
/obj/item/organ/genital/vagina/update_appearance()
|
||||
. = ..()
|
||||
var/string //Keeping this code here, so making multiple sprites for the different kinds is easier.
|
||||
icon_state = "vagina"
|
||||
var/lowershape = lowertext(shape)
|
||||
var/details
|
||||
|
||||
@@ -55,13 +55,11 @@
|
||||
if(ishuman(owner)) // Check before recasting type, although someone fucked up if you're not human AND have use_skintones somehow...
|
||||
var/mob/living/carbon/human/H = owner // only human mobs have skin_tone, which we need.
|
||||
color = "#[skintone2hex(H.skin_tone)]"
|
||||
string = "vagina-s"
|
||||
icon_state += "_s"
|
||||
else
|
||||
color = "#[owner.dna.features["vag_color"]]"
|
||||
string = "vagina"
|
||||
if(ishuman(owner))
|
||||
var/mob/living/carbon/human/H = owner
|
||||
icon_state = sanitize_text(string)
|
||||
H.update_genitals()
|
||||
|
||||
/obj/item/organ/genital/vagina/get_features(mob/living/carbon/human/H)
|
||||
|
||||
|
Before Width: | Height: | Size: 47 KiB After Width: | Height: | Size: 47 KiB |
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 8.6 KiB After Width: | Height: | Size: 6.0 KiB |
|
Before Width: | Height: | Size: 4.6 KiB After Width: | Height: | Size: 4.2 KiB |
|
Before Width: | Height: | Size: 9.8 KiB After Width: | Height: | Size: 9.1 KiB |
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.0 KiB |
|
Before Width: | Height: | Size: 626 B After Width: | Height: | Size: 546 B |
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.1 KiB |
@@ -1,3 +0,0 @@
|
||||
@echo off
|
||||
|
||||
powershell -NoProfile -ExecutionPolicy Bypass -File PostCompile.ps1 -game_path %1
|
||||
@@ -1,18 +0,0 @@
|
||||
param(
|
||||
$game_path
|
||||
)
|
||||
|
||||
Write-Host "Deploying tgstation compilation..."
|
||||
|
||||
cd $game_path
|
||||
|
||||
mkdir build
|
||||
|
||||
#.github is a little special cause of the prefix
|
||||
mv .github build/.github
|
||||
|
||||
mv * build #thank god it's that easy
|
||||
|
||||
&"build/tools/deploy.sh" $game_path $game_path/build
|
||||
|
||||
Remove-Item build -Recurse
|
||||