Merge remote-tracking branch 'citadel/master' into stuff

This commit is contained in:
kevinz000
2020-02-13 04:14:44 -07:00
85 changed files with 998 additions and 457 deletions
+6 -2
View File
@@ -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" = (
+6 -13
View File
@@ -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
+1
View File
@@ -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
View File
@@ -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
+21 -5
View File
@@ -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
+1 -4
View File
@@ -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"))
+1 -12
View File
@@ -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
+22
View File
@@ -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()
+20 -17
View File
@@ -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
+2 -2
View File
@@ -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
+1 -2
View File
@@ -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()
+4 -2
View File
@@ -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>"
+9 -15
View File
@@ -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
+1 -1
View File
@@ -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()
+1 -1
View File
@@ -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)
+1 -1
View File
@@ -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()
+1
View File
@@ -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,\
+1 -1
View File
@@ -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
+8 -1
View File
@@ -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
+1 -1
View File
@@ -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
+3
View File
@@ -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
+3
View File
@@ -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)
+4
View File
@@ -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 -1
View File
@@ -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"
+13 -3
View File
@@ -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////////
//////////////
+1 -1
View File
@@ -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)
+1 -1
View File
@@ -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
+56 -36
View File
@@ -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
+9 -1
View File
@@ -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.
*/
+22
View File
@@ -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
+5 -1
View File
@@ -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"
+7 -2
View File
@@ -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
+342
View File
@@ -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.
*/
+69
View File
@@ -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.
*/
+32
View File
@@ -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"
+8 -2
View File
@@ -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."
Binary file not shown.

Before

Width:  |  Height:  |  Size: 89 KiB

After

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 104 KiB

After

Width:  |  Height:  |  Size: 106 KiB

Binary file not shown.

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.
+102 -120
View File
@@ -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)
Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.6 KiB

After

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.6 KiB

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.8 KiB

After

Width:  |  Height:  |  Size: 9.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 626 B

After

Width:  |  Height:  |  Size: 546 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

-3
View File
@@ -1,3 +0,0 @@
@echo off
powershell -NoProfile -ExecutionPolicy Bypass -File PostCompile.ps1 -game_path %1
-18
View File
@@ -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