Merge remote-tracking branch 'upstream/master' into motivation

This commit is contained in:
keronshb
2021-04-14 17:55:24 -04:00
1616 changed files with 351906 additions and 333939 deletions
+40 -8
View File
@@ -52,14 +52,46 @@ GLOBAL_LIST_INIT(bitflags, list(1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 204
#define BLOCK_FACE_ATOM_1 (1<<17)
//turf-only flags
#define NOJAUNT_1 (1<<0)
#define UNUSED_RESERVATION_TURF_1 (1<<1)
///If a turf can be made dirty at roundstart. This is also used in areas.
#define CAN_BE_DIRTY_1 (1<<2)
///Blocks lava rivers being generated on the turf.
#define NO_LAVA_GEN_1 (1<<6)
///Blocks ruins spawning on the turf.
#define NO_RUINS_1 (1<<10)
#define NOJAUNT_1 (1<<0)
#define UNUSED_RESERVATION_TURF_1 (1<<1)
/// If a turf can be made dirty at roundstart. This is also used in areas.
#define CAN_BE_DIRTY_1 (1<<2)
/// If blood cultists can draw runes or build structures on this turf
#define CULT_PERMITTED_1 (1<<3)
/// Blocks lava rivers being generated on the turf
#define NO_LAVA_GEN_1 (1<<6)
/// Blocks ruins spawning on the turf
#define NO_RUINS_1 (1<<10)
/// Should this tile be cleaned up and reinserted into an excited group?
#define EXCITED_CLEANUP_1 (1 << 13)
////////////////Area flags\\\\\\\\\\\\\\
/// If it's a valid territory for cult summoning or the CRAB-17 phone to spawn
#define VALID_TERRITORY (1<<0)
/// If blobs can spawn there and if it counts towards their score.
#define BLOBS_ALLOWED (1<<1)
/// If mining tunnel generation is allowed in this area
#define CAVES_ALLOWED (1<<2)
/// If flora are allowed to spawn in this area randomly through tunnel generation
#define FLORA_ALLOWED (1<<3)
/// If mobs can be spawned by natural random generation
#define MOB_SPAWN_ALLOWED (1<<4)
/// If megafauna can be spawned by natural random generation
#define MEGAFAUNA_SPAWN_ALLOWED (1<<5)
/// Are you forbidden from teleporting to the area? (centcom, mobs, wizard, hand teleporter)
#define NOTELEPORT (1<<6)
/// Hides area from player Teleport function.
#define HIDDEN_AREA (1<<7)
/// If false, loading multiple maps with this area type will create multiple instances.
#define UNIQUE_AREA (1<<8)
/// If people are allowed to suicide in it. Mostly for OOC stuff like minigames
#define BLOCK_SUICIDE (1<<9)
/// Can the Xenobio management console transverse this area by default?
#define XENOBIOLOGY_COMPATIBLE (1<<10)
/// If Abductors are unable to teleport in with their observation console
#define ABDUCTOR_PROOF (1<<11)
/// If an area should be hidden from power consoles, power/atmosphere alerts, etc.
#define NO_ALERTS (1<<12)
/*
These defines are used specifically with the atom/pass_flags bitmask
+4 -3
View File
@@ -13,12 +13,13 @@
#define BLOCK_Z_OUT_UP (1<<10) // Should this object block z uprise from loc?
#define BLOCK_Z_IN_DOWN (1<<11) // Should this object block z falling from above?
#define BLOCK_Z_IN_UP (1<<12) // Should this object block z uprise from below?
#define SHOVABLE_ONTO (1<<13) //called on turf.shove_act() to consider whether an object should have a niche effect (defined in their own shove_act()) when someone is pushed onto it, or do a sanity CanPass() check.
#define SHOVABLE_ONTO (1<<13)//called on turf.shove_act() to consider whether an object should have a niche effect (defined in their own shove_act()) when someone is pushed onto it, or do a sanity CanPass() check.
#define EXAMINE_SKIP (1<<14) /// Makes the Examine proc not read out this item.
/// Integrity defines for clothing (not flags but close enough)
#define CLOTHING_PRISTINE 0 // We have no damage on the clothing
#define CLOTHING_DAMAGED 1 // There's some damage on the clothing but it still has at least one functioning bodypart and can be equipped
#define CLOTHING_SHREDDED 2 // The clothing is useless and cannot be equipped unless repaired first
#define CLOTHING_DAMAGED 1 // There's some damage on the clothing but it still has at least one functioning bodypart
#define CLOTHING_SHREDDED 2 // The clothing is near useless and has their sensors broken
// If you add new ones, be sure to add them to /obj/Initialize as well for complete mapping support
+24 -7
View File
@@ -1,18 +1,35 @@
/// Transparent, let beams pass
#define SHIELD_TRANSPARENT (1<<0)
/// Flammable, takes more damage from fire
#define SHIELD_ENERGY_WEAK (1<<1)
/// Fragile, takes more damage from brute
#define SHIELD_KINETIC_WEAK (1<<2)
/// Strong against kinetic, weak against energy
#define SHIELD_KINETIC_STRONG (1<<3)
/// Strong against energy, weak against kinetic
#define SHIELD_ENERGY_STRONG (1<<4)
/// Disabler and other stamina based energy weapons boost the damage done to the sheld
#define SHIELD_DISABLER_DISRUPTED (1<<5)
/// Doesn't block ranged attacks whatsoever
#define SHIELD_NO_RANGED (1<<6)
/// Doesn't block melee attacks whatsoever
#define SHIELD_NO_MELEE (1<<7)
/// Can shield bash
#define SHIELD_CAN_BASH (1<<1)
#define SHIELD_CAN_BASH (1<<8)
/// Shield bash knockdown on wall hit
#define SHIELD_BASH_WALL_KNOCKDOWN (1<<2)
#define SHIELD_BASH_WALL_KNOCKDOWN (1<<9)
/// Shield bash always knockdown
#define SHIELD_BASH_ALWAYS_KNOCKDOWN (1<<3)
#define SHIELD_BASH_ALWAYS_KNOCKDOWN (1<<10)
/// Shield bash disarm on wall hit
#define SHIELD_BASH_WALL_DISARM (1<<4)
#define SHIELD_BASH_WALL_DISARM (1<<11)
/// Shield bash always disarm
#define SHIELD_BASH_ALWAYS_DISARM (1<<5)
#define SHIELD_BASH_ALWAYS_DISARM (1<<12)
/// You can shieldbash target someone on the ground for ground slam
#define SHIELD_BASH_GROUND_SLAM (1<<6)
#define SHIELD_BASH_GROUND_SLAM (1<<13)
/// Shield bashing someone on the ground will disarm
#define SHIELD_BASH_GROUND_SLAM_DISARM (1<<7)
#define SHIELD_BASH_GROUND_SLAM_DISARM (1<<14)
#define SHIELD_FLAGS_DEFAULT (SHIELD_CAN_BASH | SHIELD_BASH_WALL_KNOCKDOWN | SHIELD_BASH_WALL_DISARM | SHIELD_BASH_GROUND_SLAM)
+108
View File
@@ -0,0 +1,108 @@
// Keep the identifiers here below 32 characters, you can put the full display name in the actual achievement datum
#define ACHIEVEMENT_DEFAULT "default"
#define ACHIEVEMENT_SCORE "score"
//Misc Medal hub IDs
#define MEDAL_METEOR "Your Life Before Your Eyes"
#define MEDAL_PULSE "Jackpot"
#define MEDAL_TIMEWASTE "Overextended The Joke"
#define MEDAL_RODSUPLEX "Feat of Strength"
#define MEDAL_CLOWNCARKING "Round and Full"
#define MEDAL_THANKSALOT "The Best Driver"
#define MEDAL_HELBITALJANKEN "Hel-bent on Winning"
#define MEDAL_MATERIALCRAFT "Getting an Upgrade"
#define MEDAL_DISKPLEASE "Disk, Please!"
#define MEDAL_GAMER "I'm Not Important"
#define MEDAL_VENDORSQUISH "Teenage Anarchist"
#define MEDAL_SWIRLIE "Bowl-d"
#define MEDAL_SELFOUCH "Hands???"
#define MEDAL_SANDMAN "Mister Sandman"
#define MEDAL_CLEANBOSS "Cleanboss"
#define MEDAL_RULE8 "Rule 8"
#define MEDAL_LONGSHIFT "longshift"
#define MEDAL_SNAIL "KKKiiilll mmmeee"
#define MEDAL_LOOKOUTSIR "Look Out, Sir!"
#define MEDAL_GOTTEM "GOTTEM"
#define MEDAL_ASCENSION "Ascension"
#define MEDAL_FRENCHING "FrenchingTheBubble"
#define MEDAL_ASH_ASCENSION "Ash"
#define MEDAL_FLESH_ASCENSION "Flesh"
#define MEDAL_RUST_ASCENSION "Rust"
#define MEDAL_VOID_ASCENSION "Void"
#define MEDAL_TOOLBOX_SOUL "Toolsoul"
#define MEDAL_CHEM_TUT "Beginner Chemist"
//Skill medal hub IDs
#define MEDAL_LEGENDARY_MINER "Legendary Miner"
//Mafia medal hub IDs (wins)
#define MAFIA_MEDAL_ASSISTANT "Assistant"
#define MAFIA_MEDAL_DETECTIVE "Detective"
#define MAFIA_MEDAL_PSYCHOLOGIST "Psychologist"
#define MAFIA_MEDAL_CHAPLAIN "Chaplain"
#define MAFIA_MEDAL_MD "Medical Doctor"
#define MAFIA_MEDAL_OFFICER "Security Officer"
#define MAFIA_MEDAL_LAWYER "Lawyer"
#define MAFIA_MEDAL_HOP "Head of Personnel"
#define MAFIA_MEDAL_HOS "Head of Security"
#define MAFIA_MEDAL_WARDEN "Warden"
#define MAFIA_MEDAL_CHANGELING "CHANGELING"
#define MAFIA_MEDAL_THOUGHTFEEDER "Thoughtfeeder"
#define MAFIA_MEDAL_TRAITOR "Traitor"
#define MAFIA_MEDAL_NIGHTMARE "Nightmare"
#define MAFIA_MEDAL_FUGITIVE "Fugitive"
#define MAFIA_MEDAL_OBSESSED "Obsessed"
#define MAFIA_MEDAL_CLOWN "Clown"
//Mafia medal hub IDs (misc stuff)
#define MAFIA_MEDAL_HATED "Universally Hated"
#define MAFIA_MEDAL_CHARISMATIC "Charismatic"
#define MAFIA_MEDAL_VIP "VIP"
//Boss medals
// Medal hub IDs for boss medals (Pre-fixes)
#define BOSS_MEDAL_ANY "Boss Killer"
#define BOSS_MEDAL_MINER "Blood-drunk Miner Killer"
#define BOSS_MEDAL_FROSTMINER "Demonic-frost Miner Killer"
#define BOSS_MEDAL_BUBBLEGUM "Bubblegum Killer"
#define BOSS_MEDAL_COLOSSUS "Colossus Killer"
#define BOSS_MEDAL_DRAKE "Drake Killer"
#define BOSS_MEDAL_HIEROPHANT "Hierophant Killer"
#define BOSS_MEDAL_LEGION "Legion Killer"
#define BOSS_MEDAL_TENDRIL "Tendril Exterminator"
#define BOSS_MEDAL_SWARMERS "Swarmer Beacon Killer"
#define BOSS_MEDAL_WENDIGO "Wendigo Killer"
#define BOSS_MEDAL_KINGGOAT "King Goat Killer"
#define BOSS_MEDAL_MINER_CRUSHER "Blood-drunk Miner Crusher"
#define BOSS_MEDAL_FROSTMINER_CRUSHER "Demonic-frost Miner Crusher"
#define BOSS_MEDAL_BUBBLEGUM_CRUSHER "Bubblegum Crusher"
#define BOSS_MEDAL_COLOSSUS_CRUSHER "Colossus Crusher"
#define BOSS_MEDAL_DRAKE_CRUSHER "Drake Crusher"
#define BOSS_MEDAL_HIEROPHANT_CRUSHER "Hierophant Crusher"
#define BOSS_MEDAL_LEGION_CRUSHER "Legion Crusher"
#define BOSS_MEDAL_SWARMERS_CRUSHER "Swarmer Beacon Crusher"
#define BOSS_MEDAL_WENDIGO_CRUSHER "Wendigo Crusher"
#define BOSS_MEDAL_KINGGOAT_CRUSHER "King Goat Crusher"
// Medal hub IDs for boss-kill scores
#define BOSS_SCORE "Bosses Killed"
#define MINER_SCORE "BDMs Killed"
#define FROST_MINER_SCORE "DFMs Killed"
#define BUBBLEGUM_SCORE "Bubblegum Killed"
#define COLOSSUS_SCORE "Colossus Killed"
#define DRAKE_SCORE "Drakes Killed"
#define HIEROPHANT_SCORE "Hierophants Killed"
#define LEGION_SCORE "Legion Killed"
#define SWARMER_BEACON_SCORE "Swarmer Beacs Killed"
#define WENDIGO_SCORE "Wendigos Killed"
#define KINGGOAT_SCORE "King Goat Killed"
#define TENDRIL_CLEAR_SCORE "Tendrils Killed"
// DB ID for hardcore random mode
#define HARDCORE_RANDOM_SCORE "Hardcore Random Score"
// DB ID for amount of consumed maintenance pills
#define MAINTENANCE_PILL_SCORE "Maintenance Pill Score"
+1
View File
@@ -80,6 +80,7 @@ GLOBAL_LIST_EMPTY(living_heart_cache) //A list of all living hearts in existance
#define PATH_ASH "Ash"
#define PATH_RUST "Rust"
#define PATH_FLESH "Flesh"
#define PATH_VOID "Void"
//Overthrow time to update heads obj
#define OBJECTIVE_UPDATING_TIME 300
+3
View File
@@ -281,6 +281,9 @@ GLOBAL_LIST_INIT(atmos_adjacent_savings, list(0,0))
#define CALCULATE_ADJACENT_TURFS(T) SSadjacent_air.queue[T] = 1
#endif
//If you're doing spreading things related to atmos, DO NOT USE CANATMOSPASS, IT IS NOT CHEAP. use this instead, the info is cached after all. it's tweaked just a bit to allow for circular checks
#define TURFS_CAN_SHARE(T1, T2) (LAZYACCESS(T2.atmos_adjacent_turfs, T1) || LAZYLEN(T1.atmos_adjacent_turfs & T2.atmos_adjacent_turfs))
GLOBAL_VAR(atmos_extools_initialized) // this must be an uninitialized (null) one or init_monstermos will be called twice because reasons
#define ATMOS_EXTOOLS_CHECK if(!GLOB.atmos_extools_initialized){\
GLOB.atmos_extools_initialized=TRUE;\
+5
View File
@@ -0,0 +1,5 @@
// Shipping methods
#define SHIPPING_METHOD_LTSRBT "LTSRBT" // The BEST way of shipping items: accurate, "undetectable"
#define SHIPPING_METHOD_TELEPORT "Teleport" // Picks a random area to teleport the item to and gives you a minute to get there before it is sent.
#define SHIPPING_METHOD_LAUNCH "Launch" // Throws the item from somewhere at the station.
-6
View File
@@ -1,6 +0,0 @@
#define BSQL_EXTERNAL_CONFIGURATION
#define BSQL_DEL_PROC(path) ##path/Destroy()
#define BSQL_DEL_CALL(obj) qdel(##obj)
#define BSQL_IS_DELETED(obj) (QDELETED(obj))
#define BSQL_PROTECT_DATUM(path) GENERAL_PROTECT_DATUM(##path)
#define BSQL_ERROR(message) SSdbcore.ReportError(message)
-135
View File
@@ -1,135 +0,0 @@
//BSQL - DMAPI
#define BSQL_VERSION "v1.3.0.0"
//types of connections
#define BSQL_CONNECTION_TYPE_MARIADB "MySql"
#define BSQL_CONNECTION_TYPE_SQLSERVER "SqlServer"
#define BSQL_DEFAULT_TIMEOUT 5
#define BSQL_DEFAULT_THREAD_LIMIT 50
//Call this before rebooting or shutting down your world to clean up gracefully. This invalidates all active connection and operation datums
/world/proc/BSQL_Shutdown()
return
/*
Called whenever a library call is made with verbose information, override and do with as you please
message: English debug message
*/
/world/proc/BSQL_Debug(msg)
return
/*
Create a new database connection, does not perform the actual connect
connection_type: The BSQL connection_type to use
asyncTimeout: The timeout to use for normal operations, 0 for infinite, defaults to BSQL_DEFAULT_TIMEOUT
blockingTimeout: The timeout to use for blocking operations, must be less than or equal to asyncTimeout, 0 for infinite, defaults to asyncTimeout
threadLimit: The limit of additional threads BSQL will run simultaneously, defaults to BSQL_DEFAULT_THREAD_LIMIT
*/
/datum/BSQL_Connection/New(connection_type, asyncTimeout, blockingTimeout, threadLimit)
return ..()
/*
Starts an operation to connect to a database. Should only have 1 successful call
ipaddress: The ip/hostname of the target server
port: The port of the target server
username: The username to login to the target server
password: The password for the target server
database: Optional database to connect to. Must be used when trying to do database operations, `USE x` is not sufficient
Returns: A /datum/BSQL_Operation representing the connection or null if an error occurred
*/
/datum/BSQL_Connection/proc/BeginConnect(ipaddress, port, username, password, database)
return
/*
Properly quotes a string for use by the database. The connection must be open for this proc to succeed
str: The string to quote
Returns: The string quoted on success, null on error
*/
/datum/BSQL_Connection/proc/Quote(str)
return
/*
Starts an operation for a query
query: The text of the query. Only one query allowed per invocation, no semicolons
Returns: A /datum/BSQL_Operation/Query representing the running query and subsequent result set or null if an error occurred
Note for MariaDB: The underlying connection is pooled. In order to use connection state based properties (i.e. LAST_INSERT_ID()) you can guarantee multiple queries will use the same connection by running BSQL_DEL_CALL(query) on the finished /datum/BSQL_Operation/Query and then creating the next one with another call to BeginQuery() with no sleeps in between
*/
/datum/BSQL_Connection/proc/BeginQuery(query)
return
/*
Checks if the operation is complete. This, in some cases must be called multiple times with false return before a result is present regardless of timespan. For best performance check it once per tick
Returns: TRUE if the operation is complete, FALSE if it's not, null on error
*/
/datum/BSQL_Operation/proc/IsComplete()
return
/*
Blocks the entire game until the given operation completes. IsComplete should not be checked after calling this to avoid potential side effects.
Returns: TRUE on success, FALSE if the operation wait time exceeded the connection's blockingTimeout setting
*/
/datum/BSQL_Operation/proc/WaitForCompletion()
return
/*
Get the error message associated with an operation. Should not be used while IsComplete() returns FALSE
Returns: The error message, if any. null otherwise
*/
/datum/BSQL_Operation/proc/GetError()
return
/*
Get the error code associated with an operation. Should not be used while IsComplete() returns FALSE
Returns: The error code, if any. null otherwise
*/
/datum/BSQL_Operation/proc/GetErrorCode()
return
/*
Gets an associated list of column name -> value representation of the most recent row in the query. Only valid if IsComplete() returns TRUE. If this returns null and no errors are present there are no more results in the query. Important to note that once IsComplete() returns TRUE it must not be called again without checking this or the row values may be lost
Returns: An associated list of column name -> value for the row. Values will always be either strings or null
*/
/datum/BSQL_Operation/Query/proc/CurrentRow()
return
/*
Code configuration options below
Define this to avoid modifying this file but the following defines must be declared somewhere else before BSQL/includes.dm is included
*/
#ifndef BSQL_EXTERNAL_CONFIGURATION
//Modify this if you disagree with byond's GC schemes. Ensure this is called for all connections and operations when they are deleted or they will leak native resources until /world/proc/BSQL_Shutdown() is called
#define BSQL_DEL_PROC(path) ##path/Del()
//The equivalent of calling del() in your codebase
#define BSQL_DEL_CALL(obj) del(##obj)
//Returns TRUE if an object is delete
#define BSQL_IS_DELETED(obj) (obj == null)
//Modify this to add protections to the connection and query datums
#define BSQL_PROTECT_DATUM(path)
//Modify this to change up error handling for the library
#define BSQL_ERROR(message) CRASH("BSQL: [##message]")
#endif
/*
Copyright 2018 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.
*/
+24 -14
View File
@@ -30,26 +30,36 @@
#define POD_SHAPE_NORML 1
#define POD_SHAPE_OTHER 2
#define POD_TRANSIT "1"
#define POD_FALLING "2"
#define POD_OPENING "3"
#define POD_LEAVING "4"
#define SUPPLYPOD_X_OFFSET -16
/// The baseline unit for cargo crates. Adjusting this will change the cost of all in-game shuttles, crate export values, bounty rewards, and all supply pack import values, as they use this as their unit of measurement.
#define CARGO_CRATE_VALUE 200
GLOBAL_LIST_EMPTY(supplypod_loading_bays)
GLOBAL_LIST_INIT(podstyles, list(\
list(POD_SHAPE_NORML, "pod", TRUE, "default", "yellow", RUBBLE_NORMAL, "supply pod", "A Nanotrasen supply drop pod."),\
list(POD_SHAPE_NORML, "advpod", TRUE, "bluespace", "blue", RUBBLE_NORMAL, "bluespace supply pod" , "A Nanotrasen Bluespace supply pod. Teleports back to CentCom after delivery."),\
list(POD_SHAPE_NORML, "advpod", TRUE, "centcom", "blue", RUBBLE_NORMAL, "\improper CentCom supply pod", "A Nanotrasen supply pod, this one has been marked with Central Command's designations. Teleports back to CentCom after delivery."),\
list(POD_SHAPE_NORML, "darkpod", TRUE, "syndicate", "red", RUBBLE_NORMAL, "blood-red supply pod", "An intimidating supply pod, covered in the blood-red markings of the Syndicate. It's probably best to stand back from this."),\
list(POD_SHAPE_NORML, "darkpod", TRUE, "deathsquad", "blue", RUBBLE_NORMAL, "\improper Deathsquad drop pod", "A Nanotrasen drop pod. This one has been marked the markings of Nanotrasen's elite strike team."),\
list(POD_SHAPE_NORML, "pod", TRUE, "cultist", "red", RUBBLE_NORMAL, "bloody supply pod", "A Nanotrasen supply pod covered in scratch-marks, blood, and strange runes."),\
list(POD_SHAPE_OTHER, "missile", FALSE, FALSE, FALSE, RUBBLE_THIN, "cruise missile", "A big ass missile that didn't seem to fully detonate. It was likely launched from some far-off deep space missile silo. There appears to be an auxillery payload hatch on the side, though manually opening it is likely impossible."),\
list(POD_SHAPE_OTHER, "smissile", FALSE, FALSE, FALSE, RUBBLE_THIN, "\improper Syndicate cruise missile", "A big ass, blood-red missile that didn't seem to fully detonate. It was likely launched from some deep space Syndicate missile silo. There appears to be an auxillery payload hatch on the side, though manually opening it is likely impossible."),\
list(POD_SHAPE_OTHER, "box", TRUE, FALSE, FALSE, RUBBLE_WIDE, "\improper Aussec supply crate", "An incredibly sturdy supply crate, designed to withstand orbital re-entry. Has 'Aussec Armory - 2532' engraved on the side."),\
list(POD_SHAPE_NORML, "clownpod", TRUE, "clown", "green", RUBBLE_NORMAL, "\improper HONK pod", "A brightly-colored supply pod. It likely originated from the Clown Federation."),\
list(POD_SHAPE_OTHER, "orange", TRUE, FALSE, FALSE, RUBBLE_NONE, "\improper Orange", "An angry orange."),\
list(POD_SHAPE_OTHER, FALSE, FALSE, FALSE, FALSE, RUBBLE_NONE, "\improper S.T.E.A.L.T.H. pod MKVII", "A supply pod that, under normal circumstances, is completely invisible to conventional methods of detection. How are you even seeing this?"),\
list(POD_SHAPE_OTHER, "gondola", FALSE, FALSE, FALSE, RUBBLE_NONE, "gondola", "The silent walker. This one seems to be part of a delivery agency."),\
list(POD_SHAPE_OTHER, FALSE, FALSE, FALSE, FALSE, RUBBLE_NONE, FALSE, FALSE, "rl_click", "give_po")\
list(POD_SHAPE_NORML, "pod", TRUE, "default", "yellow", RUBBLE_NORMAL, "supply pod", "A Nanotrasen supply drop pod."),\
list(POD_SHAPE_NORML, "advpod", TRUE, "bluespace", "blue", RUBBLE_NORMAL, "bluespace supply pod" , "A Nanotrasen Bluespace supply pod. Teleports back to CentCom after delivery."),\
list(POD_SHAPE_NORML, "advpod", TRUE, "centcom", "blue", RUBBLE_NORMAL, "\improper CentCom supply pod", "A Nanotrasen supply pod, this one has been marked with Central Command's designations. Teleports back to CentCom after delivery."),\
list(POD_SHAPE_NORML, "darkpod", TRUE, "syndicate", "red", RUBBLE_NORMAL, "blood-red supply pod", "An intimidating supply pod, covered in the blood-red markings of the Syndicate. It's probably best to stand back from this."),\
list(POD_SHAPE_NORML, "darkpod", TRUE, "deathsquad", "blue", RUBBLE_NORMAL, "\improper Deathsquad drop pod", "A Nanotrasen drop pod. This one has been marked the markings of Nanotrasen's elite strike team."),\
list(POD_SHAPE_NORML, "pod", TRUE, "cultist", "red", RUBBLE_NORMAL, "bloody supply pod", "A Nanotrasen supply pod covered in scratch-marks, blood, and strange runes."),\
list(POD_SHAPE_OTHER, "missile", FALSE, FALSE, FALSE, RUBBLE_THIN, "cruise missile", "A big ass missile that didn't seem to fully detonate. It was likely launched from some far-off deep space missile silo. There appears to be an auxillery payload hatch on the side, though manually opening it is likely impossible."),\
list(POD_SHAPE_OTHER, "smissile", FALSE, FALSE, FALSE, RUBBLE_THIN, "\improper Syndicate cruise missile", "A big ass, blood-red missile that didn't seem to fully detonate. It was likely launched from some deep space Syndicate missile silo. There appears to be an auxillery payload hatch on the side, though manually opening it is likely impossible."),\
list(POD_SHAPE_OTHER, "box", TRUE, FALSE, FALSE, RUBBLE_WIDE, "\improper Aussec supply crate", "An incredibly sturdy supply crate, designed to withstand orbital re-entry. Has 'Aussec Armory - 2532' engraved on the side."),\
list(POD_SHAPE_NORML, "clownpod", TRUE, "clown", "green", RUBBLE_NORMAL, "\improper HONK pod", "A brightly-colored supply pod. It likely originated from the Clown Federation."),\
list(POD_SHAPE_OTHER, "orange", TRUE, FALSE, FALSE, RUBBLE_NONE, "\improper Orange", "An angry orange."),\
list(POD_SHAPE_OTHER, FALSE, FALSE, FALSE, FALSE, RUBBLE_NONE, "\improper S.T.E.A.L.T.H. pod MKVII", "A supply pod that, under normal circumstances, is completely invisible to conventional methods of detection. How are you even seeing this?"),\
list(POD_SHAPE_OTHER, "gondola", FALSE, FALSE, FALSE, RUBBLE_NONE, "gondola", "The silent walker. This one seems to be part of a delivery agency."),\
list(POD_SHAPE_OTHER, FALSE, FALSE, FALSE, FALSE, RUBBLE_NONE, FALSE, FALSE, "rl_click", "give_po")\
))
//cit
#define PACK_GOODY_NONE 0
#define PACK_GOODY_PUBLIC 1 //can be bought by both privates and cargo
#define PACK_GOODY_PRIVATE 2 //can be bought only by privates
+6
View File
@@ -52,3 +52,9 @@
#define COLOR_ASSEMBLY_BLUE "#38559E"
#define COLOR_ASSEMBLY_PURPLE "#6F6192"
#define COLOR_ASSEMBLY_PINK "#ff4adc"
#define COLOR_WHITE "#FFFFFF"
#define COLOR_VERY_LIGHT_GRAY "#EEEEEE"
#define COLOR_SILVER "#C0C0C0"
#define COLOR_GRAY "#808080"
#define COLOR_HALF_TRANSPARENT_BLACK "#0000007A"
+2
View File
@@ -22,3 +22,5 @@
#define POLICYCONFIG_ON_DEFIB_LATE "ON_DEFIB_LATE"
/// Displayed to pyroclastic slimes on spawn
#define POLICYCONFIG_ON_PYROCLASTIC_SENTIENT "PYROCLASTIC_SLIME"
/// Displayed to pAIs on spawn
#define POLICYCONFIG_PAI "PAI_SPAWN"
+18 -5
View File
@@ -22,8 +22,12 @@
#define COMPONENT_GLOB_BLOCK_CINEMATIC 1
// signals from globally accessible objects
/// from SSsun when the sun changes position : (azimuth)
/// from SSsun when the sun changes position : (primary_sun, suns)
#define COMSIG_SUN_MOVED "sun_moved"
/// from SSactivity for things that add threat but aren't "global" (e.g. phylacteries)
#define COMSIG_THREAT_CALC "threat_calculation"
//////////////////////////////////////////////////////////////////
// /datum signals
@@ -166,9 +170,15 @@
#define COMSIG_AREA_EXITED "area_exited" //from base of area/Exited(): (atom/movable/M)
// /turf signals
#define COMSIG_TURF_CHANGE "turf_change" //from base of turf/ChangeTurf(): (path, list/new_baseturfs, flags, list/transferring_comps)
#define COMSIG_TURF_HAS_GRAVITY "turf_has_gravity" //from base of atom/has_gravity(): (atom/asker, list/forced_gravities)
#define COMSIG_TURF_MULTIZ_NEW "turf_multiz_new" //from base of turf/New(): (turf/source, direction)
///from base of turf/ChangeTurf(): (path, list/new_baseturfs, flags, list/transferring_comps)
#define COMSIG_TURF_CHANGE "turf_change"
///from base of atom/has_gravity(): (atom/asker, list/forced_gravities)
#define COMSIG_TURF_HAS_GRAVITY "turf_has_gravity"
///from base of turf/multiz_turf_del(): (turf/source, direction)
#define COMSIG_TURF_MULTIZ_DEL "turf_multiz_del"
///from base of turf/multiz_turf_new: (turf/source, direction)
#define COMSIG_TURF_MULTIZ_NEW "turf_multiz_new"
// /atom/movable signals
#define COMSIG_MOVABLE_PRE_MOVE "movable_pre_move" ///from base of atom/movable/Moved(): (/atom)
@@ -290,7 +300,7 @@
#define COMSIG_LIVING_ACTIVE_BLOCK_START "active_block_start" //from base of mob/living/keybind_start_active_blocking(): (obj/item/blocking_item, list/backup_items)
#define COMPONENT_PREVENT_BLOCK_START 1
#define COMSIG_LIVING_ACTIVE_PARRY_START "active_parry_start" //from base of mob/living/initiate_parry_sequence(): (parrying_method, datum/parrying_item_mob_or_art, list/backup_items)
#define COMSIG_LIVING_ACTIVE_PARRY_START "active_parry_start" //from base of mob/living/initiate_parry_sequence(): (parrying_method, datum/parrying_item_mob_or_art, list/backup_items, list/override)
#define COMPONENT_PREVENT_PARRY_START 1
//ALL OF THESE DO NOT TAKE INTO ACCOUNT WHETHER AMOUNT IS 0 OR LOWER AND ARE SENT REGARDLESS!
@@ -462,6 +472,9 @@
#define COMSIG_CLEAR_MOOD_EVENT "clear_mood" //Called when you clear a mood event from anywhere in the code.
#define COMSIG_MODIFY_SANITY "modify_sanity" //Called when you want to increase or decrease sanity from anywhere in the code.
///Mask of Madness
#define COMSIG_VOID_MASK_ACT "void_mask_act"
//NTnet
#define COMSIG_COMPONENT_NTNET_RECEIVE "ntnet_receive" //called on an object by its NTNET connection component on receive. (sending_id(number), sending_netname(text), data(datum/netdata))
+3
View File
@@ -12,6 +12,9 @@
#define MAX_GRANT_SCI 5000
#define MAX_GRANT_SECMEDSRV 3000
//What should vending machines charge when you buy something in-department.
#define VENDING_DISCOUNT 0 // price * discount so 0 = 0
#define ACCOUNT_CIV "CIV"
#define ACCOUNT_CIV_NAME "Civil Budget"
#define ACCOUNT_ENG "ENG"
+1 -1
View File
@@ -19,7 +19,7 @@
#define INSTRUMENT_EXP_FALLOFF_MAX 10
/// Minimum volume for when the sound is considered dead.
#define INSTRUMENT_MIN_SUSTAIN_DROPOFF 0
#define INSTRUMENT_MIN_SUSTAIN_DROPOFF 1
#define SUSTAIN_LINEAR 1
#define SUSTAIN_EXPONENTIAL 2
+26 -6
View File
@@ -17,7 +17,7 @@ GLOBAL_LIST_INIT(turfs_without_ground, typecacheof(list(
/turf/open/chasm,
/turf/open/lava,
/turf/open/water,
/turf/open/transparent/openspace
/turf/open/openspace
)))
#define isgroundlessturf(A) (is_type_in_typecache(A, GLOB.turfs_without_ground))
@@ -44,7 +44,7 @@ GLOBAL_LIST_INIT(turfs_without_ground, typecacheof(list(
#define isplatingturf(A) (istype(A, /turf/open/floor/plating))
#define istransparentturf(A) (istype(A, /turf/open/transparent)||istype(A, /turf/open/space/transparent))
#define istransparentturf(A) (istype(A, /turf/open/openspace))
//Mobs
#define isliving(A) (istype(A, /mob/living))
@@ -59,6 +59,7 @@ GLOBAL_LIST_INIT(turfs_without_ground, typecacheof(list(
//Human sub-species
#define isabductor(A) (is_species(A, /datum/species/abductor))
#define isgolem(A) (is_species(A, /datum/species/golem))
#define isclockworkgolem(A) (is_species(A, /datum/species/golem/clockwork/no_scrap))
#define islizard(A) (is_species(A, /datum/species/lizard))
#define isplasmaman(A) (is_species(A, /datum/species/plasmaman))
#define ispodperson(A) (is_species(A, /datum/species/pod))
@@ -67,17 +68,19 @@ GLOBAL_LIST_INIT(turfs_without_ground, typecacheof(list(
#define isslimeperson(A) (is_species(A, /datum/species/jelly/slime))
#define isluminescent(A) (is_species(A, /datum/species/jelly/luminescent))
#define iszombie(A) (is_species(A, /datum/species/zombie))
#define isskeleton(A) (is_species(A, /datum/species/skeleton))
#define ismoth(A) (is_species(A, /datum/species/moth))
#define ishumanbasic(A) (is_species(A, /datum/species/human))
#define iscatperson(A) (ishumanbasic(A) && istype(A.dna.species, /datum/species/human/felinid))
#define isdwarf(A) (is_species(A, /datum/species/dwarf))
#define isethereal(A) (is_species(A, /datum/species/ethereal))
#define isvampire(A) (is_species(A,/datum/species/vampire))
#define isdullahan(A) (is_species(A, /datum/species/dullahan))
#define isangel(A) (is_species(A, /datum/species/angel))
#define isvampire(A) (is_species(A, /datum/species/vampire))
#define ismush(A) (is_species(A, /datum/species/mush))
#define isshadow(A) (is_species(A, /datum/species/shadow))
#define isskeleton(A) (is_species(A, /datum/species/skeleton))
#define isrobotic(A) (is_species(A, /datum/species/ipc) || is_species(A, /datum/species/synthliz))
#define isethereal(A) (is_species(A, /datum/species/ethereal))
#define isdwarf(A) (is_species(A, /datum/species/dwarf))
// Citadel specific species
#define isipcperson(A) (is_species(A, /datum/species/ipc))
@@ -143,6 +146,10 @@ GLOBAL_LIST_INIT(turfs_without_ground, typecacheof(list(
#define ishostile(A) (istype(A, /mob/living/simple_animal/hostile))
// #define israt(A) (istype(A, /mob/living/simple_animal/hostile/rat))
// #define isregalrat(A) (istype(A, /mob/living/simple_animal/hostile/regalrat))
#define isswarmer(A) (istype(A, /mob/living/simple_animal/hostile/swarmer))
#define isguardian(A) (istype(A, /mob/living/simple_animal/hostile/guardian))
@@ -155,6 +162,7 @@ GLOBAL_LIST_INIT(turfs_without_ground, typecacheof(list(
#define isclown(A) (istype(A, /mob/living/simple_animal/hostile/retaliate/clown))
//Misc mobs
#define isobserver(A) (istype(A, /mob/dead/observer))
@@ -184,6 +192,8 @@ GLOBAL_LIST_INIT(turfs_without_ground, typecacheof(list(
#define isitem(A) (istype(A, /obj/item))
#define isstack(A) (istype(A, /obj/item/stack))
#define isgrenade(A) (istype(A, /obj/item/grenade))
#define islandmine(A) (istype(A, /obj/effect/mine))
@@ -206,6 +216,8 @@ GLOBAL_LIST_INIT(turfs_without_ground, typecacheof(list(
#define isclothing(A) (istype(A, /obj/item/clothing))
#define iscash(A) (istype(A, /obj/item/coin) || istype(A, /obj/item/stack/spacecash) || istype(A, /obj/item/holochip))
#define isbodypart(A) (istype(A, /obj/item/bodypart))
#define isprojectile(A) (istype(A, /obj/item/projectile))
@@ -214,6 +226,8 @@ GLOBAL_LIST_INIT(turfs_without_ground, typecacheof(list(
#define isfood(A) (istype(A, /obj/item/reagent_containers/food/snacks))
#define iscontainer(A) (istype(A, /obj/structure/reagent_dispensers))
//Assemblies
#define isassembly(O) (istype(O, /obj/item/assembly))
@@ -240,3 +254,9 @@ GLOBAL_LIST_INIT(glass_sheet_types, typecacheof(list(
#define isshuttleturf(T) (length(T.baseturfs) && (/turf/baseturf_skipover/shuttle in T.baseturfs))
#define isProbablyWallMounted(O) (O.pixel_x > 20 || O.pixel_x < -20 || O.pixel_y > 20 || O.pixel_y < -20)
#define isbook(O) (is_type_in_typecache(O, GLOB.book_types))
GLOBAL_LIST_INIT(book_types, typecacheof(list(
/obj/item/book,
/obj/item/spellbook,
/obj/item/storage/book)))
+1
View File
@@ -24,3 +24,4 @@
#define LANGUAGE_STONER "stoner"
#define LANGUAGE_VASSAL "vassal"
#define LANGUAGE_VOICECHANGE "voicechange"
#define LANGUAGE_MULTILINGUAL "multilingual"
+9
View File
@@ -82,3 +82,12 @@
#define LOADOUT_CAN_NAME (1<<0) //renaming items
#define LOADOUT_CAN_DESCRIPTION (1<<1) //adding a custom description to items
#define LOADOUT_CAN_COLOR_POLYCHROMIC (1<<2)
//the names of the customization tabs
#define SETTINGS_TAB 0
#define GAME_PREFERENCES_TAB 1
#define APPEARANCE_TAB 2
#define SPEECH_TAB 3
#define LOADOUT_TAB 4
#define CONTENT_PREFERENCES_TAB 5
#define KEYBINDINGS_TAB 6
+18
View File
@@ -42,6 +42,7 @@ require only minor tweaks.
#define ZTRAIT_SNOWSTORM "Weather_Snowstorm"
#define ZTRAIT_ASHSTORM "Weather_Ashstorm"
#define ZTRAIT_ACIDRAIN "Weather_Acidrain"
#define ZTRAIT_VOIDSTORM "Weather_Voidstorm"
// number - bombcap is multiplied by this before being applied to bombs
#define ZTRAIT_BOMBCAP_MULTIPLIER "Bombcap Multiplier"
@@ -106,6 +107,23 @@ require only minor tweaks.
#define PLACE_LAVA_RUIN "lavaland" //On lavaland ruin z levels(s)
#define PLACE_BELOW "below" //On z levl below - centered on same tile
#define PLACE_ISOLATED "isolated" //On isolated ruin z level
///Map generation defines
#define PERLIN_LAYER_HEIGHT "perlin_height"
#define PERLIN_LAYER_HUMIDITY "perlin_humidity"
#define PERLIN_LAYER_HEAT "perlin_heat"
#define BIOME_LOW_HEAT "low_heat"
#define BIOME_LOWMEDIUM_HEAT "lowmedium_heat"
#define BIOME_HIGHMEDIUM_HEAT "highmedium_heat"
#define BIOME_HIGH_HEAT "high_heat"
#define BIOME_LOW_HUMIDITY "low_humidity"
#define BIOME_LOWMEDIUM_HUMIDITY "lowmedium_humidity"
#define BIOME_HIGHMEDIUM_HUMIDITY "highmedium_humidity"
#define BIOME_HIGH_HUMIDITY "high_humidity"
//Map type stuff.
#define MAP_TYPE_STATION "station"
+8
View File
@@ -205,6 +205,14 @@
#define RULE_OF_THREE(a, b, x) ((a*x)/b)
// )
/// Converts a probability/second chance to probability/delta_time chance
/// For example, if you want an event to happen with a 10% per second chance, but your proc only runs every 5 seconds, do `if(prob(100*DT_PROB_RATE(0.1, 5)))`
#define DT_PROB_RATE(prob_per_second, delta_time) (1 - (1 - (prob_per_second)) ** (delta_time))
/// Like DT_PROB_RATE but easier to use, simply put `if(DT_PROB(10, 5))`
#define DT_PROB(prob_per_second_percent, delta_time) (prob(100*DT_PROB_RATE((prob_per_second_percent)/100, (delta_time))))
// )
#define MANHATTAN_DISTANCE(a, b) (abs(a.x - b.x) + abs(a.y - b.y))
#define LOGISTIC_FUNCTION(L,k,x,x_0) (L/(1+(NUM_E**(-k*(x-x_0)))))
-29
View File
@@ -1,29 +0,0 @@
// Medal names
#define BOSS_KILL_MEDAL "Killer"
#define ALL_KILL_MEDAL "Exterminator" //Killing all of x type
#define BOSS_KILL_MEDAL_CRUSHER "Crusher"
//Defines for boss medals
#define BOSS_MEDAL_MINER "Blood-drunk Miner"
#define BOSS_MEDAL_BUBBLEGUM "Bubblegum"
#define BOSS_MEDAL_COLOSSUS "Colossus"
#define BOSS_MEDAL_DRAKE "Drake"
#define BOSS_MEDAL_HIEROPHANT "Hierophant"
#define BOSS_MEDAL_LEGION "Legion"
#define BOSS_MEDAL_TENDRIL "Tendril"
#define BOSS_MEDAL_SWARMERS "Swarmer Beacon"
// Score names
#define HIEROPHANT_SCORE "Hierophants Killed"
#define BOSS_SCORE "Bosses Killed"
#define BUBBLEGUM_SCORE "Bubblegum Killed"
#define COLOSSUS_SCORE "Colossus Killed"
#define DRAKE_SCORE "Drakes Killed"
#define LEGION_SCORE "Legion Killed"
#define SWARMER_BEACON_SCORE "Swarmer Beacons Killed"
#define TENDRIL_CLEAR_SCORE "Tendrils Killed"
//Misc medals
#define MEDAL_METEOR "Your Life Before Your Eyes"
#define MEDAL_PULSE "Jackpot"
#define MEDAL_TIMEWASTE "Overextended The Joke"
+21 -5
View File
@@ -30,7 +30,8 @@ Will print: "/mob/living/carbon/human/death" (you can optionally embed it in a s
//Human Overlays Indexes/////////
//LOTS OF CIT CHANGES HERE. BE CAREFUL WHEN UPSTREAM ADDS MORE LAYERS
#define MUTATIONS_LAYER 33 //mutations. Tk headglows, cold resistance glow, etc
#define MUTATIONS_LAYER 34 //mutations. Tk headglows, cold resistance glow, etc
#define ANTAG_LAYER 33 //stuff for things like cultism indicators (clock cult glow, cultist red halos, whatever else new that comes up)
#define GENITALS_BEHIND_LAYER 32 //Some genitalia needs to be behind everything, such as with taurs (Taurs use body_behind_layer
#define BODY_BEHIND_LAYER 31 //certain mutantrace features (tail when looking south) that must appear behind the body parts
#define BODYPARTS_LAYER 30 //Initially "AUGMENTS", this was repurposed to be a catch-all bodyparts flag
@@ -63,7 +64,7 @@ Will print: "/mob/living/carbon/human/death" (you can optionally embed it in a s
#define HANDS_LAYER 3
#define BODY_FRONT_LAYER 2
#define FIRE_LAYER 1 //If you're on fire
#define TOTAL_LAYERS 33 //KEEP THIS UP-TO-DATE OR SHIT WILL BREAK ;_;
#define TOTAL_LAYERS 34 //KEEP THIS UP-TO-DATE OR SHIT WILL BREAK ;_;
//Human Overlay Index Shortcuts for alternate_worn_layer, layers
//Because I *KNOW* somebody will think layer+1 means "above"
@@ -173,8 +174,18 @@ GLOBAL_LIST_EMPTY(bloody_footprints_cache)
#define BROKEN_SENSORS -1
#define NO_SENSORS 0
#define HAS_SENSORS 1
#define LOCKED_SENSORS 2
#define DAMAGED_SENSORS_LIVING 1
#define DAMAGED_SENSORS_VITALS 2
#define HAS_SENSORS 3
//suit sensor flags: sensor_flag defines
#define SENSOR_RANDOM (1<<0)
#define SENSOR_LOCKED (1<<1)
//suit sensor integrity percentage threshold defines
#define SENSOR_INTEGRITY_COORDS 0.2
#define SENSOR_INTEGRITY_VITALS 0.6
#define SENSOR_INTEGRITY_BINARY 1
//Wet floor type flags. Stronger ones should be higher in number.
#define TURF_DRY (0)
@@ -435,8 +446,13 @@ GLOBAL_LIST_INIT(pda_reskins, list(PDA_SKIN_CLASSIC = 'icons/obj/pda.dmi', PDA_S
//text files
#define BRAIN_DAMAGE_FILE "traumas.json"
#define ION_FILE "ion_laws.json"
#define REDPILL_FILE "redpill.json"
#define PIRATE_NAMES_FILE "pirates.json"
#define REDPILL_FILE "redpill.json"
#define ARCADE_FILE "arcade.json"
// #define BOOMER_FILE "boomer.json"
// #define LOCATIONS_FILE "locations.json"
// #define WANTED_FILE "wanted_message.json"
// #define VISTA_FILE "steve.json"
#define FLESH_SCAR_FILE "wounds/flesh_scar_desc.json"
#define BONE_SCAR_FILE "wounds/bone_scar_desc.json"
#define SCAR_LOC_FILE "wounds/scar_loc.json"
+3
View File
@@ -340,3 +340,6 @@
#define EXAMINE_MORE_TIME 1 SECONDS
#define SILENCE_RANGED_MESSAGE (1<<0)
///Define for spawning megafauna instead of a mob for cave gen
#define SPAWN_MEGAFAUNA "bluh bluh huge boss"
+2 -2
View File
@@ -21,10 +21,10 @@
#define STIMULUM_FIRST_DROP 0.065
#define STIMULUM_SECOND_RISE 0.0009
#define STIMULUM_ABSOLUTE_DROP 0.00000335
#define REACTION_OPPRESSION_THRESHOLD 5
#define REACTION_OPPRESSION_THRESHOLD 10
#define NOBLIUM_FORMATION_ENERGY 2e9 //1 Mole of Noblium takes the planck energy to condense.
//Research point amounts
#define NOBLIUM_RESEARCH_AMOUNT 1000
#define NOBLIUM_RESEARCH_AMOUNT 25
#define BZ_RESEARCH_SCALE 4
#define BZ_RESEARCH_MAX_AMOUNT 400
#define MIASMA_RESEARCH_AMOUNT 6
+15 -6
View File
@@ -8,7 +8,7 @@
#define DEFAULT_SCAN_RANGE 7 //default view range for finding targets.
//Mode defines
//Mode defines. If you add a new one make sure you update mode_name in /mob/living/simple_animal/bot
#define BOT_IDLE 0 // idle
#define BOT_HUNT 1 // found target, hunting
#define BOT_PREP_ARREST 2 // at target, preparing to arrest
@@ -27,7 +27,8 @@
#define BOT_NAV 15 // computing navigation
#define BOT_WAIT_FOR_NAV 16 // waiting for nav computation
#define BOT_NO_ROUTE 17 // no destination beacon found (or no route)
#define BOT_TIPPED 18 // someone tipped a medibot over ;_;
#define BOT_SHOWERSTANCE 18 // cleaning unhygienic humans
#define BOT_TIPPED 19 // someone tipped a medibot over ;_;
//Bot types
#define SEC_BOT (1<<0) // Secutritrons (Beepsky) and ED-209s
@@ -37,6 +38,7 @@
#define MED_BOT (1<<4) // Medibots
#define HONK_BOT (1<<5) // Honkbots & ED-Honks
#define FIRE_BOT (1<<6) // Firebots
#define HYGIENE_BOT (1<<7) // Hygienebots
//AI notification defines
#define NEW_BORG 1
@@ -70,7 +72,14 @@
#define BORG_SEC_AVAILABLE (!CONFIG_GET(flag/disable_secborg) && GLOB.security_level >= CONFIG_GET(number/minimum_secborg_alert))
//silicon_priviledges flags
#define PRIVILEDGES_SILICON (1<<0)
#define PRIVILEDGES_PAI (1<<1)
#define PRIVILEDGES_BOT (1<<2)
#define PRIVILEDGES_DRONE (1<<3)
#define PRIVILEGES_SILICON (1<<0)
#define PRIVILEGES_PAI (1<<1)
#define PRIVILEGES_BOT (1<<2)
#define PRIVILEGES_DRONE (1<<3)
#define BORG_LAMP_CD_RESET -1 //special value to reset cyborg's lamp_cooldown
/// Defines for whether or not module slots are broken.
#define BORG_MODULE_ALL_DISABLED (1<<0)
#define BORG_MODULE_TWO_DISABLED (1<<1)
#define BORG_MODULE_THREE_DISABLED (1<<2)
+3
View File
@@ -41,10 +41,13 @@
#define ROLE_GHOSTCAFE "ghostcafe"
#define ROLE_MINOR_ANTAG "minorantag"
#define ROLE_RESPAWN "respawnsystem"
/// Not an actual antag. Lets players force all antags off.
#define ROLE_NO_ANTAGONISM "NO_ANTAGS"
//Missing assignment means it's not a gamemode specific role, IT'S NOT A BUG OR ERROR.
//The gamemode specific ones are just so the gamemodes can query whether a player is old enough
//(in game days played) to play that role
GLOBAL_LIST_INIT(special_roles, list(
ROLE_NO_ANTAGONISM,
ROLE_TRAITOR = /datum/game_mode/traitor,
ROLE_BROTHER = /datum/game_mode/traitor/bros,
ROLE_OPERATIVE = /datum/game_mode/nuclear,
+5 -2
View File
@@ -86,13 +86,16 @@
#define EMOTE_OMNI 4
//Don't set this very much higher then 1024 unless you like inviting people in to dos your server with message spam
#define MAX_MESSAGE_LEN 2048 //Citadel edit: What's the WORST that could happen?
#define MAX_FLAVOR_LEN 4096 //double the maximum message length.
#define MAX_MESSAGE_LEN 4096 //Citadel edit: What's the WORST that could happen?
#define MAX_FLAVOR_LEN 4096
#define MAX_TASTE_LEN 40 //lick... vore... ew...
#define MAX_NAME_LEN 42
#define MAX_BROADCAST_LEN 512
#define MAX_CHARTER_LEN 80
// Is something in the IC chat filter? This is config dependent.
#define CHAT_FILTER_CHECK(T) (config.ic_filter_regex && findtext(T, config.ic_filter_regex))
// Audio/Visual Flags. Used to determine what sense are required to notice a message.
#define MSG_VISUAL (1<<0)
#define MSG_AUDIBLE (1<<1)
+9
View File
@@ -106,6 +106,9 @@
#define STATUS_EFFECT_FAKE_VIRUS /datum/status_effect/fake_virus //gives you fluff messages for cough, sneeze, headache, etc but without an actual virus
#define STATUS_EFFECT_NO_COMBAT_MODE /datum/status_effect/no_combat_mode //Wont allow combat mode and will disable it
#define STATUS_EFFECT_STASIS /datum/status_effect/grouped/stasis //Halts biological functions like bleeding, chemical processing, blood regeneration, walking, etc
#define STATUS_EFFECT_MESMERIZE /datum/status_effect/mesmerize //Just reskinned no_combat_mode
#define STATUS_EFFECT_ELECTROSTAFF /datum/status_effect/electrostaff //slows down victim
@@ -138,3 +141,9 @@
#define STATUS_EFFECT_RAINBOWPROTECTION /datum/status_effect/rainbow_protection //Invulnerable and pacifistic
#define STATUS_EFFECT_SLIMESKIN /datum/status_effect/slimeskin //Increased armor
#define STATUS_EFFECT_DNA_MELT /datum/status_effect/dna_melt //usually does something horrible to you when you hit 100 genetic instability
/////////////
// GROUPED //
/////////////
#define STASIS_ASCENSION_EFFECT "heretic_ascension"
+4 -3
View File
@@ -108,7 +108,7 @@
#define INIT_ORDER_SOUNDS 83
#define INIT_ORDER_INSTRUMENTS 82
#define INIT_ORDER_VIS 80
// #define INIT_ORDER_ACHIEVEMENTS 77
#define INIT_ORDER_ACHIEVEMENTS 77
#define INIT_ORDER_RESEARCH 75
#define INIT_ORDER_EVENTS 70
#define INIT_ORDER_JOBS 65
@@ -152,6 +152,7 @@
// If the subsystem isn't listed here it's either DEFAULT or PROCESS (if it's a processing subsystem child)
#define FIRE_PRIORITY_VORE 5
#define FIRE_PRIORITY_ACTIVITY 10
#define FIRE_PRIORITY_IDLE_NPC 10
#define FIRE_PRIORITY_SERVER_MAINT 10
#define FIRE_PRIORITY_RESEARCH 10
@@ -204,7 +205,7 @@
///Compile all the overlays for an atom from the cache lists
// |= on overlays is not actually guaranteed to not add same appearances but we're optimistically using it anyway.
#define COMPILE_OVERLAYS(A)\
if (TRUE) {\
do {\
var/list/ad = A.add_overlays;\
var/list/rm = A.remove_overlays;\
if(LAZYLEN(rm)){\
@@ -216,7 +217,7 @@
ad.Cut();\
}\
A.flags_1 &= ~OVERLAY_QUEUED_1;\
}
} while(FALSE)
/**
+10 -10
View File
@@ -1,6 +1,6 @@
// tgstation-server DMAPI
#define TGS_DMAPI_VERSION "5.2.10"
#define TGS_DMAPI_VERSION "5.2.9"
// All functions and datums outside this document are subject to change with any version and should not be relied on.
@@ -67,7 +67,7 @@
#define TGS_EVENT_REPO_CHECKOUT 1
/// When the repository performs a fetch operation. No parameters
#define TGS_EVENT_REPO_FETCH 2
/// When the repository test merges. Parameters: PR Number, PR Sha, (Nullable) Comment made by TGS user
/// When the repository merges a pull request. Parameters: PR Number, PR Sha, (Nullable) Comment made by TGS user
#define TGS_EVENT_REPO_MERGE_PULL_REQUEST 3
/// Before the repository makes a sychronize operation. Parameters: Absolute repostiory path
#define TGS_EVENT_REPO_PRE_SYNCHRONIZE 4
@@ -190,21 +190,21 @@
/// Represents a merge of a GitHub pull request.
/datum/tgs_revision_information/test_merge
/// The test merge number.
/// The pull request number.
var/number
/// The test merge source's title when it was merged.
/// The pull request title when it was merged.
var/title
/// The test merge source's body when it was merged.
/// The pull request body when it was merged.
var/body
/// The Username of the test merge source's author.
/// The GitHub username of the pull request's author.
var/author
/// An http URL to the test merge source.
/// An http URL to the pull request.
var/url
/// The SHA of the test merge when that was merged.
/// The SHA of the pull request when that was merged.
var/pull_request_commit
/// ISO 8601 timestamp of when the test merge was created on TGS.
/// ISO 8601 timestamp of when the pull request was merged.
var/time_merged
/// Optional comment left by the TGS user who initiated the merge.
/// (Nullable) Comment left by the TGS user who initiated the merge..
var/comment
/// Represents a connected chat channel.
+6
View File
@@ -191,12 +191,14 @@
#define TRAIT_MUSICIAN "musician"
#define TRAIT_PERMABONER "permanent_arousal"
#define TRAIT_NEVERBONER "never_aroused"
#define TRAIT_NYMPHO "nymphomaniac"
#define TRAIT_MASO "masochism"
#define TRAIT_HIGH_BLOOD "high_blood"
#define TRAIT_PARA "paraplegic"
#define TRAIT_EMPATH "empath"
#define TRAIT_FRIENDLY "friendly"
#define TRAIT_SNOB "snob"
#define TRAIT_MULTILINGUAL "multilingual"
#define TRAIT_CULT_EYES "cult_eyes"
#define TRAIT_AUTO_CATCH_ITEM "auto_catch_item"
#define TRAIT_CLOWN_MENTALITY "clown_mentality" // The future is now, clownman.
@@ -281,6 +283,7 @@
#define SHOES_TRAIT "shoes" //inherited from your sweet kicks
#define GLOVE_TRAIT "glove" //inherited by your cool gloves
#define BOOK_TRAIT "granter (book)" // knowledge is power
#define TURF_TRAIT "turf"
// unique trait sources, still defines
#define STATUE_TRAIT "statue"
@@ -321,12 +324,15 @@
#define ABDUCTOR_ANTAGONIST "abductor-antagonist"
#define MADE_UNCLONEABLE "made-uncloneable"
#define TIMESTOP_TRAIT "timestop"
#define DOMAIN_TRAIT "domain"
#define NUKEOP_TRAIT "nuke-op"
#define CLOWNOP_TRAIT "clown-op"
#define MEGAFAUNA_TRAIT "megafauna"
#define DEATHSQUAD_TRAIT "deathsquad"
#define SLIMEPUDDLE_TRAIT "slimepuddle"
#define CORRUPTED_SYSTEM "corrupted-system"
///Turf trait for when a turf is transparent
#define TURF_Z_TRANSPARENT_TRAIT "turf_z_transparent"
/// This trait is added by the active directional block system.
#define ACTIVE_BLOCK_TRAIT "active_block"
/// This trait is added by the parry system.
+3
View File
@@ -90,6 +90,9 @@
#define VV_HK_TRIGGER_EMP "empulse"
#define VV_HK_TRIGGER_EXPLOSION "explode"
#define VV_HK_AUTO_RENAME "auto_rename"
// #define VV_HK_RADIATE "radiate"
#define VV_HK_EDIT_FILTERS "edit_filters"
// #define VV_HK_ADD_AI "add_ai"
// /obj
#define VV_HK_OSAY "osay"
+31 -3
View File
@@ -15,7 +15,7 @@
#define LAZYREMOVE(L, I) if(L) { L -= I; if(!length(L)) { L = null; } }
#define LAZYADD(L, I) if(!L) { L = list(); } L += I;
#define LAZYOR(L, I) if(!L) { L = list(); } L |= I;
#define LAZYFIND(L, V) L ? L.Find(V) : 0
#define LAZYFIND(L, V) (L ? L.Find(V) : 0)
#define LAZYACCESS(L, I) (L ? (isnum(I) ? (I > 0 && I <= length(L) ? L[I] : null) : L[I]) : null)
#define LAZYSET(L, K, V) if(!L) { L = list(); } L[K] = V;
#define LAZYLEN(L) length(L)
@@ -66,7 +66,7 @@
} while(FALSE)
//Returns a list in plain english as a string
/proc/english_list(list/input, nothing_text = "nothing", and_text = " and ", comma_text = ", ", final_comma_text = "" )
/proc/english_list(list/input, nothing_text = "nothing", and_text = " and ", comma_text = ", ", final_comma_text = "")
var/total = length(input)
switch(total)
if (0)
@@ -87,6 +87,34 @@
return "[output][and_text][input[index]]"
/**
* English_list but associative supporting. Higher overhead.
*/
/proc/english_list_assoc(list/input, nothing_text = "nothing", and_text = " and ", comma_text = ", ", final_comma_text = "")
var/total = length(input)
switch(total)
if (0)
return "[nothing_text]"
if (1)
var/assoc = input[input[1]] == null? "" : " = [input[input[1]]]"
return "[input[1]][assoc]"
if (2)
var/assoc = input[input[1]] == null? "" : " = [input[input[1]]]"
var/assoc2 = input[input[2]] == null? "" : " = [input[input[2]]]"
return "[input[1]][assoc][and_text][input[2]][assoc2]"
else
var/output = ""
var/index = 1
var/assoc
while (index < total)
if (index == total - 1)
comma_text = final_comma_text
assoc = input[input[index]] == null? "" : " = [input[input[index]]]"
output += "[input[index]][assoc][comma_text]"
++index
assoc = input[input[index]] == null? "" : " = [input[input[index]]]"
return "[output][and_text][input[index]]"
//Returns list element or null. Should prevent "index out of bounds" error.
/proc/listgetindex(list/L, index)
if(LAZYLEN(L))
@@ -586,7 +614,7 @@
used_key_list[input_key] = 1
return input_key
#if DM_VERSION > 513
#if DM_VERSION > 514
#error Remie said that lummox was adding a way to get a lists
#error contents via list.values, if that is true remove this
#error otherwise, update the version and bug lummox
+2 -2
View File
@@ -231,10 +231,10 @@
src_object = window.locked_by.src_object
// Insert src_object info
if(src_object)
entry += "\nUsing: [src_object.type] [REF(src_object)]"
entry += "Using: [src_object.type] [REF(src_object)]"
// Insert message
if(message)
entry += "\n[message]"
entry += "[message]"
WRITE_LOG(GLOB.tgui_log, entry)
/* Close open log handles. This should be called as late as possible, and no logging should hapen after. */
+14 -16
View File
@@ -1,8 +1,8 @@
#define BP_MAX_ROOM_SIZE 300
GLOBAL_LIST_INIT(typecache_powerfailure_safe_areas, typecacheof(/area/engine/engineering, \
/area/engine/supermatter, \
/area/engine/atmospherics_engine, \
GLOBAL_LIST_INIT(typecache_powerfailure_safe_areas, typecacheof(/area/engineering/main, \
/area/engineering/supermatter, \
/area/engineering/atmospherics_engine, \
/area/ai_monitored/turret_protected/ai))
//Repopulates sortedAreas list
@@ -85,13 +85,12 @@ GLOBAL_LIST_INIT(typecache_powerfailure_safe_areas, typecacheof(/area/engine/eng
if(target_z == 0 || target_z == T.z)
turfs += T
return turfs
// Gets an atmos isolated contained space
// Returns an associative list of turf|dirs pairs
// The dirs are connected turfs in the same space
// break_if_found is a typecache of turf/area types to return false if found
// Please keep this proc type agnostic. If you need to restrict it do it elsewhere or add an arg.
/proc/detect_room(turf/origin, list/break_if_found)
/proc/detect_room(turf/origin, list/break_if_found, max_size=INFINITY)
if(origin.blocks_air)
return list(origin)
@@ -103,6 +102,8 @@ GLOBAL_LIST_INIT(typecache_powerfailure_safe_areas, typecacheof(/area/engine/eng
found_turfs.Cut(1, 2)
var/dir_flags = checked_turfs[sourceT]
for(var/dir in GLOB.alldirs)
if(length(.) > max_size)
return
if(dir_flags & dir) // This means we've checked this dir before, probably from the other turf
continue
var/turf/checkT = get_step(sourceT, dir)
@@ -115,7 +116,7 @@ GLOBAL_LIST_INIT(typecache_powerfailure_safe_areas, typecacheof(/area/engine/eng
if(break_if_found[checkT.type] || break_if_found[checkT.loc.type])
return FALSE
var/static/list/cardinal_cache = list("[NORTH]"=TRUE, "[EAST]"=TRUE, "[SOUTH]"=TRUE, "[WEST]"=TRUE)
if(!cardinal_cache["[dir]"] || checkT.blocks_air || !CANATMOSPASS(sourceT, checkT))
if(!cardinal_cache["[dir]"] || checkT.blocks_air || !TURFS_CAN_SHARE(sourceT, checkT))
continue
found_turfs += checkT // Since checkT is connected, add it to the list to be processed
@@ -130,25 +131,24 @@ GLOBAL_LIST_INIT(typecache_powerfailure_safe_areas, typecacheof(/area/engine/eng
/area/space,
))
if(creator)
if(creator.create_area_cooldown >= world.time)
to_chat(creator, "<span class='warning'>You're trying to create a new area a little too fast.</span>")
return
creator.create_area_cooldown = world.time + 10
if(creator?.create_area_cooldown >= world.time)
to_chat(creator, "<span class='warning'>You're trying to create a new area a little too fast.</span>")
return
creator.create_area_cooldown = world.time + 10
var/list/turfs = detect_room(get_turf(creator), area_or_turf_fail_types)
var/list/turfs = detect_room(get_turf(creator), area_or_turf_fail_types, BP_MAX_ROOM_SIZE*2)
if(!turfs)
to_chat(creator, "<span class='warning'>The new area must be completely airtight and not a part of a shuttle.</span>")
return
if(turfs.len > BP_MAX_ROOM_SIZE)
to_chat(creator, "<span class='warning'>The room you're in is too big. It is [((turfs.len / BP_MAX_ROOM_SIZE)-1)*100]% larger than allowed.</span>")
to_chat(creator, "<span class='warning'>The room you're in is too big. It is [turfs.len >= BP_MAX_ROOM_SIZE *2 ? "more than 100" : ((turfs.len / BP_MAX_ROOM_SIZE)-1)*100]% larger than allowed.</span>")
return
var/list/areas = list("New Area" = /area)
for(var/i in 1 to turfs.len)
var/area/place = get_area(turfs[i])
if(blacklisted_areas[place.type])
continue
if(!place.requires_power || place.noteleport || place.hidden)
if(!place.requires_power || (place.area_flags & NOTELEPORT) || (place.area_flags & HIDDEN_AREA))
continue // No expanding powerless rooms etc
areas[place.name] = place
var/area_choice = input(creator, "Choose an area to expand or make a new area.", "Area Expansion") as null|anything in areas
@@ -170,7 +170,6 @@ GLOBAL_LIST_INIT(typecache_powerfailure_safe_areas, typecacheof(/area/engine/eng
newA.setup(str)
newA.set_dynamic_lighting()
newA.has_gravity = oldA.has_gravity
newA.noteleport = oldA.noteleport
else
newA = area_choice
@@ -190,7 +189,6 @@ GLOBAL_LIST_INIT(typecache_powerfailure_safe_areas, typecacheof(/area/engine/eng
to_chat(creator, "<span class='notice'>You have created a new area, named [newA.name]. It is now weather proof, and constructing an APC will allow it to be powered.</span>")
return TRUE
/**
* Returns the base area the target is located in if there is one.
* Alternatively, returns the area as is.
+74
View File
@@ -0,0 +1,74 @@
/*
Here's how to use the chat system with configs
send2adminchat is a simple function that broadcasts to admin channels
send2chat is a bit verbose but can be very specific
The second parameter is a string, this string should be read from a config.
What this does is dictacte which TGS4 channels can be sent to.
For example if you have the following channels in tgs4 set up
- Channel 1, Tag: asdf
- Channel 2, Tag: bombay,asdf
- Channel 3, Tag: Hello my name is asdf
- Channel 4, No Tag
- Channel 5, Tag: butts
and you make the call:
send2chat("I sniff butts", CONFIG_GET(string/where_to_send_sniff_butts))
and the config option is set like:
WHERE_TO_SEND_SNIFF_BUTTS asdf
It will be sent to channels 1 and 2
Alternatively if you set the config option to just:
WHERE_TO_SEND_SNIFF_BUTTS
it will be sent to all connected chats.
In TGS3 it will always be sent to all connected designated game chats.
*/
/**
* Sends a message to TGS chat channels.
*
* message - The message to send.
* channel_tag - Required. If "", the message with be sent to all connected (Game-type for TGS3) channels. Otherwise, it will be sent to TGS4 channels with that tag (Delimited by ','s).
*/
/proc/send2chat(message, channel_tag)
if(channel_tag == null || !world.TgsAvailable())
return
var/datum/tgs_version/version = world.TgsVersion()
if(channel_tag == "" || 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
var/list/applicable_tags = splittext(channel.custom_tag, ",")
if(channel_tag in applicable_tags)
channels_to_use += channel
if(channels_to_use.len)
world.TgsChatBroadcast(message, channels_to_use)
/**
* Sends a message to TGS admin chat channels.
*
* category - The category of the mssage.
* message - The message to send.
*/
/proc/send2adminchat(category, message, embed_links = FALSE)
category = replacetext(replacetext(category, "\proper", ""), "\improper", "")
message = replacetext(replacetext(message, "\proper", ""), "\improper", "")
// if(!embed_links)
// message = GLOB.has_discord_embeddable_links.Replace(replacetext(message, "`", ""), " ```$1``` ")
world.TgsTargetedChatBroadcast("[category] | [message]", TRUE)
+319
View File
@@ -0,0 +1,319 @@
#define ICON_NOT_SET "Not Set"
//This is stored as a nested list instead of datums or whatever because it json encodes nicely for usage in tgui
GLOBAL_LIST_INIT(master_filter_info, list(
"alpha" = list(
"defaults" = list(
"x" = 0,
"y" = 0,
"icon" = ICON_NOT_SET,
"render_source" = "",
"flags" = 0
),
"flags" = list(
"MASK_INVERSE" = MASK_INVERSE,
"MASK_SWAP" = MASK_SWAP
)
),
"angular_blur" = list(
"defaults" = list(
"x" = 0,
"y" = 0,
"size" = 1
)
),
/* Not supported because making a proper matrix editor on the frontend would be a huge dick pain.
Uncomment if you ever implement it
"color" = list(
"defaults" = list(
"color" = matrix(),
"space" = FILTER_COLOR_RGB
)
),
*/
"displace" = list(
"defaults" = list(
"x" = 0,
"y" = 0,
"size" = null,
"icon" = ICON_NOT_SET,
"render_source" = ""
)
),
"drop_shadow" = list(
"defaults" = list(
"x" = 1,
"y" = -1,
"size" = 1,
"offset" = 0,
"color" = COLOR_HALF_TRANSPARENT_BLACK
)
),
"blur" = list(
"defaults" = list(
"size" = 1
)
),
"layer" = list(
"defaults" = list(
"x" = 0,
"y" = 0,
"icon" = ICON_NOT_SET,
"render_source" = "",
"flags" = FILTER_OVERLAY,
"color" = "",
"transform" = null,
"blend_mode" = BLEND_DEFAULT
)
),
"motion_blur" = list(
"defaults" = list(
"x" = 0,
"y" = 0
)
),
"outline" = list(
"defaults" = list(
"size" = 0,
"color" = COLOR_BLACK,
"flags" = NONE
),
"flags" = list(
"OUTLINE_SHARP" = OUTLINE_SHARP,
"OUTLINE_SQUARE" = OUTLINE_SQUARE
)
),
"radial_blur" = list(
"defaults" = list(
"x" = 0,
"y" = 0,
"size" = 0.01
)
),
"rays" = list(
"defaults" = list(
"x" = 0,
"y" = 0,
"size" = 16,
"color" = COLOR_WHITE,
"offset" = 0,
"density" = 10,
"threshold" = 0.5,
"factor" = 0,
"flags" = FILTER_OVERLAY | FILTER_UNDERLAY
),
"flags" = list(
"FILTER_OVERLAY" = FILTER_OVERLAY,
"FILTER_UNDERLAY" = FILTER_UNDERLAY
)
),
"ripple" = list(
"defaults" = list(
"x" = 0,
"y" = 0,
"size" = 1,
"repeat" = 2,
"radius" = 0,
"falloff" = 1,
"flags" = NONE
),
"flags" = list(
"WAVE_BOUNDED" = WAVE_BOUNDED
)
),
"wave" = list(
"defaults" = list(
"x" = 0,
"y" = 0,
"size" = 1,
"offset" = 0,
"flags" = NONE
),
"flags" = list(
"WAVE_SIDEWAYS" = WAVE_SIDEWAYS,
"WAVE_BOUNDED" = WAVE_BOUNDED
)
)
))
#undef ICON_NOT_SET
//Helpers to generate lists for filter helpers
//This is the only practical way of writing these that actually produces sane lists
/proc/alpha_mask_filter(x, y, icon/icon, render_source, flags)
. = list("type" = "alpha")
if(!isnull(x))
.["x"] = x
if(!isnull(y))
.["y"] = y
if(!isnull(icon))
.["icon"] = icon
if(!isnull(render_source))
.["render_source"] = render_source
if(!isnull(flags))
.["flags"] = flags
/proc/angular_blur_filter(x, y, size)
. = list("type" = "angular_blur")
if(!isnull(x))
.["x"] = x
if(!isnull(y))
.["y"] = y
if(!isnull(size))
.["size"] = size
/proc/color_matrix_filter(matrix/in_matrix, space)
. = list("type" = "color")
.["color"] = in_matrix
if(!isnull(space))
.["space"] = space
/proc/displacement_map_filter(icon, render_source, x, y, size = 32)
. = list("type" = "displace")
if(!isnull(icon))
.["icon"] = icon
if(!isnull(render_source))
.["render_source"] = render_source
if(!isnull(x))
.["x"] = x
if(!isnull(y))
.["y"] = y
if(!isnull(size))
.["size"] = size
/proc/drop_shadow_filter(x, y, size, offset, color)
. = list("type" = "drop_shadow")
if(!isnull(x))
.["x"] = x
if(!isnull(y))
.["y"] = y
if(!isnull(size))
.["size"] = size
if(!isnull(offset))
.["offset"] = offset
if(!isnull(color))
.["color"] = color
/proc/gauss_blur_filter(size)
. = list("type" = "blur")
if(!isnull(size))
.["size"] = size
/proc/layering_filter(icon, render_source, x, y, flags, color, transform, blend_mode)
. = list("type" = "layer")
if(!isnull(icon))
.["icon"] = icon
if(!isnull(render_source))
.["render_source"] = render_source
if(!isnull(x))
.["x"] = x
if(!isnull(y))
.["y"] = y
if(!isnull(color))
.["color"] = color
if(!isnull(flags))
.["flags"] = flags
if(!isnull(transform))
.["transform"] = transform
if(!isnull(blend_mode))
.["blend_mode"] = blend_mode
/proc/motion_blur_filter(x, y)
. = list("type" = "motion_blur")
if(!isnull(x))
.["x"] = x
if(!isnull(y))
.["y"] = y
/proc/outline_filter(size, color, flags)
. = list("type" = "outline")
if(!isnull(size))
.["size"] = size
if(!isnull(color))
.["color"] = color
if(!isnull(flags))
.["flags"] = flags
/proc/radial_blur_filter(size, x, y)
. = list("type" = "radial_blur")
if(!isnull(size))
.["size"] = size
if(!isnull(x))
.["x"] = x
if(!isnull(y))
.["y"] = y
/proc/rays_filter(size, color, offset, density, threshold, factor, x, y, flags)
. = list("type" = "rays")
if(!isnull(size))
.["size"] = size
if(!isnull(color))
.["color"] = color
if(!isnull(offset))
.["offset"] = offset
if(!isnull(density))
.["density"] = density
if(!isnull(threshold))
.["threshold"] = threshold
if(!isnull(factor))
.["factor"] = factor
if(!isnull(x))
.["x"] = x
if(!isnull(y))
.["y"] = y
if(!isnull(flags))
.["flags"] = flags
/proc/ripple_filter(radius, size, falloff, repeat, x, y, flags)
. = list("type" = "ripple")
if(!isnull(radius))
.["radius"] = radius
if(!isnull(size))
.["size"] = size
if(!isnull(falloff))
.["falloff"] = falloff
if(!isnull(repeat))
.["repeat"] = repeat
if(!isnull(flags))
.["flags"] = flags
if(!isnull(x))
.["x"] = x
if(!isnull(y))
.["y"] = y
/proc/wave_filter(x, y, size, offset, flags)
. = list("type" = "wave")
if(!isnull(size))
.["size"] = size
if(!isnull(x))
.["x"] = x
if(!isnull(y))
.["y"] = y
if(!isnull(offset))
.["offset"] = offset
if(!isnull(flags))
.["flags"] = flags
/proc/apply_wibbly_filters(atom/in_atom, length)
for(var/i in 1 to 7)
//This is a very baffling and strange way of doing this but I am just preserving old functionality
var/X
var/Y
var/rsq
do
X = 60*rand() - 30
Y = 60*rand() - 30
rsq = X*X + Y*Y
while(rsq<100 || rsq>900) // Yeah let's just loop infinitely due to bad luck what's the worst that could happen?
var/random_roll = rand()
in_atom.add_filter("wibbly-[i]", 5, wave_filter(x = X, y = Y, size = rand() * 2.5 + 0.5, offset = random_roll))
var/filter = in_atom.get_filter("wibbly-[i]")
animate(filter, offset = random_roll, time = 0, loop = -1, flags = ANIMATION_PARALLEL)
animate(offset = random_roll - 1, time = rand() * 20 + 10)
/proc/remove_wibbly_filters(atom/in_atom)
var/filter
for(var/i in 1 to 7)
filter = in_atom.get_filter("wibbly-[i]")
animate(filter)
in_atom.remove_filter("wibbly-[i]")
+1 -1
View File
@@ -10,7 +10,7 @@
announcement += "<br><h2 class='alert'>[html_encode(title)]</h2>"
else if(type == "Captain")
announcement += "<h1 class='alert'>Captain Announces</h1>"
GLOB.news_network.SubmitArticle(text, "Captain's Announcement", "Station Announcements", null)
GLOB.news_network.SubmitArticle(html_encode(text), "Captain's Announcement", "Station Announcements", null)
else
if(!sender_override)
+243 -90
View File
@@ -1,81 +1,102 @@
#define POPCOUNT_SURVIVORS "survivors" //Not dead at roundend
#define POPCOUNT_ESCAPEES "escapees" //Not dead and on centcom/shuttles marked as escaped
#define POPCOUNT_SHUTTLE_ESCAPEES "shuttle_escapees" //Emergency shuttle only.
#define POPCOUNT_SURVIVORS "survivors" //Not dead at roundend
#define POPCOUNT_ESCAPEES "escapees" //Not dead and on centcom/shuttles marked as escaped
#define POPCOUNT_SHUTTLE_ESCAPEES "shuttle_escapees" //Emergency shuttle only.
#define PERSONAL_LAST_ROUND "personal last round"
#define SERVER_LAST_ROUND "server last round"
/datum/controller/subsystem/ticker/proc/gather_roundend_feedback()
var/datum/station_state/end_state = new /datum/station_state()
end_state.count()
station_integrity = min(PERCENT(GLOB.start_state.score(end_state)), 100)
gather_antag_data()
record_nuke_disk_location()
var/json_file = file("[GLOB.log_directory]/round_end_data.json")
// All but npcs sublists and ghost category contain only mobs with minds
var/list/file_data = list("escapees" = list("humans" = list(), "silicons" = list(), "others" = list(), "npcs" = list()), "abandoned" = list("humans" = list(), "silicons" = list(), "others" = list(), "npcs" = list()), "ghosts" = list(), "additional data" = list())
var/num_survivors = 0
var/num_escapees = 0
var/num_shuttle_escapees = 0
var/num_survivors = 0 //Count of non-brain non-camera mobs with mind that are alive
var/num_escapees = 0 //Above and on centcom z
var/num_shuttle_escapees = 0 //Above and on escape shuttle
var/list/area/shuttle_areas
if(SSshuttle && SSshuttle.emergency)
if(SSshuttle?.emergency)
shuttle_areas = SSshuttle.emergency.shuttle_areas
for(var/mob/m in GLOB.mob_list)
var/escaped
var/category
for(var/mob/M in GLOB.mob_list)
var/list/mob_data = list()
if(isnewplayer(m))
if(isnewplayer(M))
continue
if (m.client && m.client.prefs && m.client.prefs.auto_ooc)
if (!(m.client.prefs.chat_toggles & CHAT_OOC))
m.client.prefs.chat_toggles ^= CHAT_OOC
if(m.mind)
if(m.stat != DEAD && !isbrain(m) && !iscameramob(m))
// enable their ooc?
if (M.client?.prefs?.auto_ooc)
if (!(M.client.prefs.chat_toggles & CHAT_OOC))
M.client.prefs.chat_toggles ^= CHAT_OOC
var/escape_status = "abandoned" //default to abandoned
var/category = "npcs" //Default to simple count only bracket
var/count_only = TRUE //Count by name only or full info
mob_data["name"] = M.name
if(M.mind)
count_only = FALSE
mob_data["ckey"] = M.mind.key
if(M.stat != DEAD && !isbrain(M) && !iscameramob(M))
num_survivors++
mob_data += list("name" = m.name, "ckey" = ckey(m.mind.key))
if(isobserver(m))
escaped = "ghosts"
else if(isliving(m))
var/mob/living/L = m
mob_data += list("location" = get_area(L), "health" = L.health)
if(EMERGENCY_ESCAPED_OR_ENDGAMED && (M.onCentCom() || M.onSyndieBase()))
num_escapees++
escape_status = "escapees"
if(shuttle_areas[get_area(M)])
num_shuttle_escapees++
if(isliving(M))
var/mob/living/L = M
mob_data["location"] = get_area(L)
mob_data["health"] = L.health
if(ishuman(L))
var/mob/living/carbon/human/H = L
category = "humans"
mob_data += list("job" = H.mind.assigned_role, "species" = H.dna.species.name)
if(H.mind)
mob_data["job"] = H.mind.assigned_role
else
mob_data["job"] = "Unknown"
mob_data["species"] = H.dna.species.name
else if(issilicon(L))
category = "silicons"
if(isAI(L))
mob_data += list("module" = "AI")
if(isAI(L))
mob_data += list("module" = "pAI")
if(iscyborg(L))
mob_data["module"] = "AI"
else if(ispAI(L))
mob_data["module"] = "pAI"
else if(iscyborg(L))
var/mob/living/silicon/robot/R = L
mob_data += list("module" = R.module)
else
category = "others"
mob_data += list("typepath" = m.type)
if(!escaped)
if(EMERGENCY_ESCAPED_OR_ENDGAMED && (m.onCentCom() || m.onSyndieBase()))
escaped = "escapees"
num_escapees++
if(shuttle_areas[get_area(m)])
num_shuttle_escapees++
else
escaped = "abandoned"
if(!m.mind && (!ishuman(m) || !issilicon(m)))
var/list/npc_nest = file_data["[escaped]"]["npcs"]
if(npc_nest.Find(initial(m.name)))
file_data["[escaped]"]["npcs"]["[initial(m.name)]"] += 1
else
file_data["[escaped]"]["npcs"]["[initial(m.name)]"] = 1
else
if(isobserver(m))
var/pos = length(file_data["[escaped]"]) + 1
file_data["[escaped]"]["[pos]"] = mob_data
else
if(!category)
mob_data["module"] = R.module.name
else
category = "others"
mob_data += list("name" = m.name, "typepath" = m.type)
var/pos = length(file_data["[escaped]"]["[category]"]) + 1
file_data["[escaped]"]["[category]"]["[pos]"] = mob_data
mob_data["typepath"] = M.type
//Ghosts don't care about minds, but we want to retain ckey data etc
if(isobserver(M))
count_only = FALSE
escape_status = "ghosts"
if(!M.mind)
mob_data["ckey"] = M.key
category = null //ghosts are one list deep
//All other mindless stuff just gets counts by name
if(count_only)
var/list/npc_nest = file_data["[escape_status]"]["npcs"]
var/name_to_use = initial(M.name)
if(ishuman(M))
name_to_use = "Unknown Human" //Monkeymen and other mindless corpses
if(npc_nest.Find(name_to_use))
file_data["[escape_status]"]["npcs"][name_to_use] += 1
else
file_data["[escape_status]"]["npcs"][name_to_use] = 1
else
//Mobs with minds and ghosts get detailed data
if(category)
var/pos = length(file_data["[escape_status]"]["[category]"]) + 1
file_data["[escape_status]"]["[category]"]["[pos]"] = mob_data
else
var/pos = length(file_data["[escape_status]"]) + 1
file_data["[escape_status]"]["[pos]"] = mob_data
var/datum/station_state/end_state = new /datum/station_state()
end_state.count()
station_integrity = min(PERCENT(GLOB.start_state.score(end_state)), 100)
file_data["additional data"]["station integrity"] = station_integrity
WRITE_FILE(json_file, json_encode(file_data))
SSblackbox.record_feedback("nested tally", "round_end_stats", num_survivors, list("survivors", "total"))
SSblackbox.record_feedback("nested tally", "round_end_stats", num_escapees, list("escapees", "total"))
SSblackbox.record_feedback("nested tally", "round_end_stats", GLOB.joined_player_list.len, list("players", "total"))
@@ -169,10 +190,34 @@
file_data["wanted"] = list("author" = "[GLOB.news_network.wanted_issue.scannedUser]", "criminal" = "[GLOB.news_network.wanted_issue.criminal]", "description" = "[GLOB.news_network.wanted_issue.body]", "photo file" = "[GLOB.news_network.wanted_issue.photo_file]")
WRITE_FILE(json_file, json_encode(file_data))
///Handles random hardcore point rewarding if it applies.
/datum/controller/subsystem/ticker/proc/HandleRandomHardcoreScore(client/player_client)
if(!ishuman(player_client.mob))
return FALSE
var/mob/living/carbon/human/human_mob = player_client.mob
if(!human_mob.hardcore_survival_score) ///no score no glory
return FALSE
if(human_mob.mind && (human_mob.mind.special_role || length(human_mob.mind.antag_datums) > 0))
var/didthegamerwin = TRUE
for(var/a in human_mob.mind.antag_datums)
var/datum/antagonist/antag_datum = a
for(var/i in antag_datum.objectives)
var/datum/objective/objective_datum = i
if(!objective_datum.check_completion())
didthegamerwin = FALSE
if(!didthegamerwin)
return FALSE
player_client.give_award(/datum/award/score/hardcore_random, human_mob, round(human_mob.hardcore_survival_score))
else if(human_mob.onCentCom())
player_client.give_award(/datum/award/score/hardcore_random, human_mob, round(human_mob.hardcore_survival_score))
/datum/controller/subsystem/ticker/proc/declare_completion()
set waitfor = FALSE
to_chat(world, "<BR><BR><BR><span class='big bold'>The round has ended.</span>")
log_game("The round has ended.")
if(LAZYLEN(GLOB.round_end_notifiees))
world.TgsTargetedChatBroadcast("[GLOB.round_end_notifiees.Join(", ")] the round has ended.", FALSE)
@@ -186,6 +231,19 @@
C.RollCredits()
C.playtitlemusic(40)
CONFIG_SET(flag/suicide_allowed,TRUE) // EORG suicides allowed
var/speed_round = FALSE
if(world.time - SSticker.round_start_time <= 300 SECONDS)
speed_round = TRUE
for(var/client/C in GLOB.clients)
if(!C.credits)
C.RollCredits()
C.playtitlemusic(40)
if(speed_round)
C.give_award(/datum/award/achievement/misc/speed_round, C.mob)
HandleRandomHardcoreScore(C)
var/popcount = gather_roundend_feedback()
display_report(popcount)
@@ -204,7 +262,7 @@
var/survival_rate = GLOB.joined_player_list.len ? "[PERCENT(popcount[POPCOUNT_SURVIVORS]/GLOB.joined_player_list.len)]%" : "there's literally no player"
send2irc("Server", "A round of [mode.name] just ended[mode_result == "undefined" ? "." : " with a [mode_result]."] Survival rate: [survival_rate]")
send2adminchat("Server", "A round of [mode.name] just ended[mode_result == "undefined" ? "." : " with a [mode_result]."] Survival rate: [survival_rate]")
if(length(CONFIG_GET(keyed_list/cross_server)))
send_news_report()
@@ -215,6 +273,11 @@
CHECK_TICK
// handle_hearts()
set_observer_default_invisibility(0, "<span class='warning'>The round is over! You are now visible to the living.</span>")
CHECK_TICK
//These need update to actually reflect the real antagonists
//Print a list of antagonists to the server log
var/list/total_antagonists = list()
@@ -233,16 +296,13 @@
for(var/antag_name in total_antagonists)
var/list/L = total_antagonists[antag_name]
log_game("[antag_name]s :[L.Join(", ")].")
set_observer_default_invisibility(0, "<span class='warning'>The round is over! You are now visible to the living.</span>")
CHECK_TICK
SSdbcore.SetRoundEnd()
//Collects persistence features
if(mode.station_was_nuked)
SSpersistence.station_was_destroyed = TRUE
if(!mode.allow_persistence_save)
SSpersistence.station_persistence_save_disabled = TRUE
SSpersistence.CollectData()
if(mode.allow_persistence_save)
SSpersistence.SaveTCGCards()
SSpersistence.CollectData()
//stop collecting feedback during grifftime
SSblackbox.Seal()
@@ -277,11 +337,15 @@
//Antagonists
parts += antag_report()
parts += hardcore_random_report()
CHECK_TICK
//Medals
parts += medal_report()
//Station Goals
parts += goal_report()
//Economy & Money
parts += market_report()
listclearnulls(parts)
@@ -322,12 +386,15 @@
//ignore this comment, it fixes the broken sytax parsing caused by the " above
else
parts += "[FOURSPACES]<i>Nobody died this shift!</i>"
var/avg_threat = SSactivity.get_average_threat()
var/max_threat = SSactivity.get_max_threat()
parts += "[FOURSPACES]Threat at round end: [SSactivity.current_threat]"
parts += "[FOURSPACES]Average threat: [avg_threat]"
parts += "[FOURSPACES]Max threat: [max_threat]"
if(istype(SSticker.mode, /datum/game_mode/dynamic))
var/datum/game_mode/dynamic/mode = SSticker.mode
mode.update_playercounts()
parts += "[FOURSPACES]Final threat level: [mode.threat_level]"
parts += "[FOURSPACES]Final threat: [mode.threat]"
parts += "[FOURSPACES]Average threat: [mode.threat_average]"
mode.update_playercounts() // ?
parts += "[FOURSPACES]Target threat: [mode.threat_level]"
parts += "[FOURSPACES]Executed rules:"
for(var/datum/dynamic_ruleset/rule in mode.executed_rules)
parts += "[FOURSPACES][FOURSPACES][rule.ruletype] - <b>[rule.name]</b>: -[rule.cost + rule.scaled_times * rule.scaling_cost] threat"
@@ -336,27 +403,56 @@
parts += "[FOURSPACES][FOURSPACES][str]"
for(var/entry in mode.threat_tallies)
parts += "[FOURSPACES][FOURSPACES][entry] added [mode.threat_tallies[entry]]"
SSblackbox.record_feedback("tally","dynamic_threat",mode.threat_level,"Final threat level")
SSblackbox.record_feedback("tally","dynamic_threat",mode.threat,"Final Threat")
SSblackbox.record_feedback("tally","threat",mode.threat_level,"Target threat")
SSblackbox.record_feedback("tally","threat",SSactivity.current_threat,"Final Threat")
SSblackbox.record_feedback("tally","threat",avg_threat,"Average Threat")
SSblackbox.record_feedback("tally","threat",max_threat,"Max Threat")
return parts.Join("<br>")
/client/proc/roundend_report_file()
return "data/roundend_reports/[ckey].html"
/datum/controller/subsystem/ticker/proc/show_roundend_report(client/C, previous = FALSE)
/**
* Log the round-end report as an HTML file
*
* Composits the roundend report, and saves it in two locations.
* The report is first saved along with the round's logs
* Then, the report is copied to a fixed directory specifically for
* housing the server's last roundend report. In this location,
* the file will be overwritten at the end of each shift.
*/
/datum/controller/subsystem/ticker/proc/log_roundend_report()
var/roundend_file = file("[GLOB.log_directory]/round_end_data.html")
var/list/parts = list()
parts += "<div class='panel stationborder'>"
parts += GLOB.survivor_report
parts += "</div>"
parts += GLOB.common_report
var/content = parts.Join()
//Log the rendered HTML in the round log directory
fdel(roundend_file)
WRITE_FILE(roundend_file, content)
//Place a copy in the root folder, to be overwritten each round.
roundend_file = file("data/server_last_roundend_report.html")
fdel(roundend_file)
WRITE_FILE(roundend_file, content)
/datum/controller/subsystem/ticker/proc/show_roundend_report(client/C, report_type = null)
var/datum/browser/roundend_report = new(C, "roundend")
roundend_report.width = 800
roundend_report.height = 600
var/content
var/filename = C.roundend_report_file()
if(!previous)
if(report_type == PERSONAL_LAST_ROUND) //Look at this player's last round
content = file2text(filename)
else if (report_type == SERVER_LAST_ROUND) //Look at the last round that this server has seen
content = file2text("data/server_last_roundend_report.html")
else //report_type is null, so make a new report based on the current round and show that to the player
var/list/report_parts = list(personal_report(C), GLOB.common_report)
content = report_parts.Join()
remove_verb(C, /client/proc/show_previous_roundend_report)
fdel(filename)
text2file(content, filename)
else
content = file2text(filename)
roundend_report.set_content(content)
roundend_report.stylesheets = list()
roundend_report.add_stylesheet("roundend", 'html/browser/roundend.css')
@@ -393,8 +489,9 @@
/datum/controller/subsystem/ticker/proc/display_report(popcount)
GLOB.common_report = build_roundend_report()
GLOB.survivor_report = survivor_report(popcount)
log_roundend_report()
for(var/client/C in GLOB.clients)
show_roundend_report(C, FALSE)
show_roundend_report(C)
give_show_report_button(C)
CHECK_TICK
@@ -412,12 +509,11 @@
if (aiPlayer.connected_robots.len)
var/borg_num = aiPlayer.connected_robots.len
var/robolist = "<br><b>[aiPlayer.real_name]</b>'s minions were: "
parts += "<br><b>[aiPlayer.real_name]</b>'s minions were:"
for(var/mob/living/silicon/robot/robo in aiPlayer.connected_robots)
borg_num--
if(robo.mind)
robolist += "<b>[robo.name]</b>[robo.mind.hide_ckey ? "" : " (Played by: <b>[robo.mind.key]</b>)"] [robo.stat == DEAD ? " <span class='redtext'>(Deactivated)</span>" : ""][borg_num ?", ":""]<br>"
parts += "[robolist]"
parts += "<b>[robo.name]</b>[robo.mind.hide_ckey ? "" : " (Played by: <b>[robo.mind.key]</b>)"] [robo.stat == DEAD ? " <span class='redtext'>(Deactivated)</span>" : ""][borg_num ?", ":""]"
if(!borg_spacer)
borg_spacer = TRUE
@@ -444,6 +540,34 @@
parts += G.get_result()
return "<div class='panel stationborder'><ul>[parts.Join()]</ul></div>"
///Generate a report for how much money is on station, as well as the richest crewmember on the station.
/datum/controller/subsystem/ticker/proc/market_report()
var/list/parts = list()
parts += "<span class='header'>Station Economic Summary:</span>"
///This is the richest account on station at roundend.
var/datum/bank_account/mr_moneybags
///This is the station's total wealth at the end of the round.
var/station_vault = 0
///How many players joined the round.
var/total_players = GLOB.joined_player_list.len
var/list/typecache_bank = typecacheof(list(/datum/bank_account/department, /datum/bank_account/remote))
for(var/i in SSeconomy.generated_accounts)
var/datum/bank_account/current_acc = SSeconomy.generated_accounts[i]
if(typecache_bank[current_acc.type])
continue
station_vault += current_acc.account_balance
if(!mr_moneybags || mr_moneybags.account_balance < current_acc.account_balance)
mr_moneybags = current_acc
parts += "<div class='panel stationborder'>There were [station_vault] credits collected by crew this shift.<br>"
if(total_players > 0)
parts += "An average of [station_vault/total_players] credits were collected.<br>"
// log_econ("Roundend credit total: [station_vault] credits. Average Credits: [station_vault/total_players]")
if(mr_moneybags)
parts += "The most affluent crew member at shift end was <b>[mr_moneybags.account_holder] with [mr_moneybags.account_balance]</b> cr!</div>"
else
parts += "Somehow, nobody made any money this shift! This'll result in some budget cuts...</div>"
return parts
/datum/controller/subsystem/ticker/proc/medal_report()
if(GLOB.commendations.len)
var/list/parts = list()
@@ -453,16 +577,40 @@
return "<div class='panel stationborder'>[parts.Join("<br>")]</div>"
return ""
///Generate a report for all players who made it out alive with a hardcore random character and prints their final score
/datum/controller/subsystem/ticker/proc/hardcore_random_report()
. = list()
var/list/hardcores = list()
for(var/i in GLOB.player_list)
if(!ishuman(i))
continue
var/mob/living/carbon/human/human_player = i
if(!human_player.hardcore_survival_score || !human_player.onCentCom() || human_player.stat == DEAD) ///gotta escape nerd
continue
if(!human_player.mind)
continue
hardcores += human_player
if(!length(hardcores))
return
. += "<div class='panel stationborder'><span class='header'>The following people made it out as a random hardcore character:</span>"
. += "<ul class='playerlist'>"
for(var/mob/living/carbon/human/human_player in hardcores)
. += "<li>[printplayer(human_player.mind)] with a hardcore random score of [round(human_player.hardcore_survival_score)]</li>"
. += "</ul></div>"
/datum/controller/subsystem/ticker/proc/antag_report()
var/list/result = list()
var/list/all_teams = list()
var/list/all_antagonists = list()
// for(var/datum/team/A in GLOB.antagonist_teams)
// all_teams |= A
for(var/datum/antagonist/A in GLOB.antagonists)
if(!A.owner)
continue
all_teams |= A.get_team()
all_antagonists += A
all_antagonists |= A
for(var/datum/team/T in all_teams)
result += T.roundend_report()
@@ -515,9 +663,9 @@
/datum/action/report/Trigger()
if(owner && GLOB.common_report && SSticker.current_state == GAME_STATE_FINISHED)
SSticker.show_roundend_report(owner.client, FALSE)
SSticker.show_roundend_report(owner.client)
/datum/action/report/IsAvailable(silent = FALSE)
/datum/action/report/IsAvailable()
return 1
/datum/action/report/Topic(href,href_list)
@@ -532,7 +680,9 @@
var/jobtext = ""
if(ply.assigned_role)
jobtext = " the <b>[ply.assigned_role]</b>"
var/text = "<b>[ply.hide_ckey ? "<b>[ply.name]</b>[jobtext] " : "[ply.key]</b> was <b>[ply.name]</b>[jobtext] and "]"
var/text = (ply.hide_ckey ? \
"<b>[ply.key]</b> was <b>[ply.name]</b>[jobtext] and" \
: "<b>[ply.name]</b>[jobtext]")
if(ply.current)
if(ply.current.stat == DEAD)
text += " <span class='redtext'>died</span>"
@@ -589,11 +739,9 @@
var/list/sql_admins = list()
for(var/i in GLOB.protected_admins)
var/datum/admins/A = GLOB.protected_admins[i]
var/sql_ckey = sanitizeSQL(A.target)
var/sql_rank = sanitizeSQL(A.rank.name)
sql_admins += list(list("ckey" = "'[sql_ckey]'", "rank" = "'[sql_rank]'"))
sql_admins += list(list("ckey" = A.target, "rank" = A.rank.name))
SSdbcore.MassInsert(format_table_name("admin"), sql_admins, duplicate_key = TRUE)
var/datum/DBQuery/query_admin_rank_update = SSdbcore.NewQuery("UPDATE [format_table_name("player")] p INNER JOIN [format_table_name("admin")] a ON p.ckey = a.ckey SET p.lastadminrank = a.rank")
var/datum/db_query/query_admin_rank_update = SSdbcore.NewQuery("UPDATE [format_table_name("player")] p INNER JOIN [format_table_name("admin")] a ON p.ckey = a.ckey SET p.lastadminrank = a.rank")
query_admin_rank_update.Execute()
qdel(query_admin_rank_update)
@@ -626,15 +774,20 @@
flags += "can_edit_flags"
if(!flags.len)
continue
var/sql_rank = sanitizeSQL(R.name)
var/flags_to_check = flags.Join(" != [R_EVERYTHING] AND ") + " != [R_EVERYTHING]"
var/datum/DBQuery/query_check_everything_ranks = SSdbcore.NewQuery("SELECT flags, exclude_flags, can_edit_flags FROM [format_table_name("admin_ranks")] WHERE rank = '[sql_rank]' AND ([flags_to_check])")
var/datum/db_query/query_check_everything_ranks = SSdbcore.NewQuery(
"SELECT flags, exclude_flags, can_edit_flags FROM [format_table_name("admin_ranks")] WHERE rank = :rank AND ([flags_to_check])",
list("rank" = R.name)
)
if(!query_check_everything_ranks.Execute())
qdel(query_check_everything_ranks)
return
if(query_check_everything_ranks.NextRow()) //no row is returned if the rank already has the correct flag value
var/flags_to_update = flags.Join(" = [R_EVERYTHING], ") + " = [R_EVERYTHING]"
var/datum/DBQuery/query_update_everything_ranks = SSdbcore.NewQuery("UPDATE [format_table_name("admin_ranks")] SET [flags_to_update] WHERE rank = '[sql_rank]'")
var/datum/db_query/query_update_everything_ranks = SSdbcore.NewQuery(
"UPDATE [format_table_name("admin_ranks")] SET [flags_to_update] WHERE rank = :rank",
list("rank" = R.name)
)
if(!query_update_everything_ranks.Execute())
qdel(query_update_everything_ranks)
return
+2 -6
View File
@@ -13,10 +13,6 @@
* SQL sanitization
*/
// Run all strings to be used in an SQL query through this proc first to properly escape out injection attempts.
/proc/sanitizeSQL(t)
return SSdbcore.Quote("[t]")
/proc/format_table_name(table as text)
return CONFIG_GET(string/feedback_tableprefix) + table
@@ -670,7 +666,7 @@ GLOBAL_LIST_INIT(binary, list("0","1"))
if(fexists(log))
oldjson = json_decode(file2text(log))
oldentries = oldjson["data"]
if(!isemptylist(oldentries))
if(length(oldentries))
for(var/string in accepted)
for(var/old in oldentries)
if(string == old)
@@ -680,7 +676,7 @@ GLOBAL_LIST_INIT(binary, list("0","1"))
var/list/finalized = list()
finalized = accepted.Copy() + oldentries.Copy() //we keep old and unreferenced phrases near the bottom for culling
listclearnulls(finalized)
if(!isemptylist(finalized) && length(finalized) > storemax)
if(length(finalized) > storemax)
finalized.Cut(storemax + 1)
fdel(log)
+36 -49
View File
@@ -263,7 +263,7 @@ Turf and target are separate in case you want to teleport some distance from a t
return .
//Returns a list of all items of interest with their name
/proc/getpois(mobs_only=0,skip_mindless=0)
/proc/getpois(mobs_only = FALSE, skip_mindless = FALSE, specify_dead_role = TRUE)
var/list/mobs = sortmobs()
var/list/namecounts = list()
var/list/pois = list()
@@ -277,7 +277,7 @@ Turf and target are separate in case you want to teleport some distance from a t
if(M.real_name && M.real_name != M.name)
name += " \[[M.real_name]\]"
if(M.stat == DEAD)
if(M.stat == DEAD && specify_dead_role)
if(isobserver(M))
name += " \[ghost\]"
else
@@ -1030,17 +1030,19 @@ B --><-- A
A.cut_overlay(O)
/proc/get_random_station_turf()
return safepick(get_area_turfs(pick(GLOB.the_station_areas)))
var/list/turfs = get_area_turfs(pick(GLOB.the_station_areas))
if (length(turfs))
return pick(turfs)
/proc/get_safe_random_station_turf() //excludes dense turfs (like walls) and areas that have valid_territory set to FALSE
/proc/get_safe_random_station_turf(list/areas_to_pick_from = GLOB.the_station_areas) //excludes dense turfs (like walls) and areas that have valid_territory set to FALSE
for (var/i in 1 to 5)
var/list/L = get_area_turfs(pick(GLOB.the_station_areas))
var/list/L = get_area_turfs(pick(areas_to_pick_from))
var/turf/target
while (L.len && !target)
var/I = rand(1, L.len)
var/turf/T = L[I]
var/area/X = get_area(T)
if(!T.density && X.valid_territory)
if(!T.density && (X.area_flags & VALID_TERRITORY))
var/clear = TRUE
for(var/obj/O in T)
if(O.density)
@@ -1247,28 +1249,40 @@ GLOBAL_DATUM_INIT(dview_mob, /mob/dview, new)
#define FOR_DVIEW_END GLOB.dview_mob.loc = null
//can a window be here, or is there a window blocking it?
/proc/valid_window_location(turf/T, dir_to_check)
if(!T)
/**
* Checks whether the target turf is in a valid state to accept a directional window
* or other directional pseudo-dense object such as railings.
*
* Returns FALSE if the target turf cannot accept a directional window or railing.
* Returns TRUE otherwise.
*
* Arguments:
* * dest_turf - The destination turf to check for existing windows and railings
* * test_dir - The prospective dir of some atom you'd like to put on this turf.
* * is_fulltile - Whether the thing you're attempting to move to this turf takes up the entire tile or whether it supports multiple movable atoms on its tile.
*/
/proc/valid_window_location(turf/dest_turf, test_dir, is_fulltile = FALSE)
if(!dest_turf)
return FALSE
for(var/obj/O in T)
if(istype(O, /obj/machinery/door/window) && (O.dir == dir_to_check || dir_to_check == FULLTILE_WINDOW_DIR))
return FALSE
if(istype(O, /obj/structure/windoor_assembly))
var/obj/structure/windoor_assembly/W = O
if(W.ini_dir == dir_to_check || dir_to_check == FULLTILE_WINDOW_DIR)
for(var/obj/turf_content in dest_turf)
if(istype(turf_content, /obj/machinery/door/window))
if((turf_content.dir == test_dir) || is_fulltile)
return FALSE
if(istype(O, /obj/structure/window))
var/obj/structure/window/W = O
if(W.ini_dir == dir_to_check || W.ini_dir == FULLTILE_WINDOW_DIR || dir_to_check == FULLTILE_WINDOW_DIR)
if(istype(turf_content, /obj/structure/windoor_assembly))
var/obj/structure/windoor_assembly/windoor_assembly = turf_content
if(windoor_assembly.dir == test_dir || is_fulltile)
return FALSE
if(istype(O, /obj/structure/railing))
var/obj/structure/railing/rail = O
if(rail.ini_dir == dir_to_check || rail.ini_dir == FULLTILE_WINDOW_DIR || dir_to_check == FULLTILE_WINDOW_DIR)
if(istype(turf_content, /obj/structure/window))
var/obj/structure/window/window_structure = turf_content
if(window_structure.dir == test_dir || window_structure.fulltile || is_fulltile)
return FALSE
if(istype(turf_content, /obj/structure/railing))
var/obj/structure/railing/rail = turf_content
if(rail.dir == test_dir || is_fulltile)
return FALSE
return TRUE
/proc/pass()
/proc/pass(...)
return
/proc/get_mob_or_brainmob(occupant)
@@ -1579,33 +1593,6 @@ GLOBAL_DATUM_INIT(dview_mob, /mob/dview, new)
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)
return
UNTIL(GLOB.tgs_initialized)
if(!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()
//Checks to see if either the victim has a garlic necklace or garlic in their blood
/proc/blood_sucking_checks(var/mob/living/carbon/target, check_neck, check_blood)
//Bypass this if the target isnt carbon.
+7
View File
@@ -259,6 +259,13 @@ GLOBAL_LIST_INIT(bitfields, list(
),
"shield_flags" = list(
"SHIELD_TRANSPARENT" = SHIELD_TRANSPARENT,
"SHIELD_ENERGY_WEAK" = SHIELD_ENERGY_WEAK,
"SHIELD_KINETIC_WEAK" = SHIELD_KINETIC_WEAK,
"SHIELD_KINETIC_STRONG" = SHIELD_KINETIC_STRONG,
"SHIELD_ENERGY_STRONG" = SHIELD_ENERGY_STRONG,
"SHIELD_DISABLER_DISRUPTED" = SHIELD_DISABLER_DISRUPTED,
"SHIELD_NO_RANGED" = SHIELD_NO_RANGED,
"SHIELD_NO_MELEE" = SHIELD_NO_MELEE,
"SHIELD_CAN_BASH" = SHIELD_CAN_BASH,
"SHIELD_BASH_WALL_KNOCKDOWN" = SHIELD_BASH_WALL_KNOCKDOWN,
"SHIELD_BASH_ALWAYS_KNOCKDOWN" = SHIELD_BASH_ALWAYS_KNOCKDOWN,
+22 -4
View File
@@ -126,6 +126,7 @@ GLOBAL_LIST_INIT(ai_core_display_screens, list(
"Not Malf",
"Patriot",
"Pirate",
"Portrait",
"President",
"Rainbow",
"Clown",
@@ -152,14 +153,20 @@ GLOBAL_LIST_INIT(ai_core_display_screens, list(
"Yes-Man"
))
/proc/resolve_ai_icon(input)
/proc/resolve_ai_icon(input, radial_preview = FALSE)
if(!input || !(input in GLOB.ai_core_display_screens))
return "ai"
else
if(input == "Random")
input = pick(GLOB.ai_core_display_screens - "Random")
if(radial_preview)
return "ai-[lowertext(input)]"
if(input == "Random")
input = pick(GLOB.ai_core_display_screens - "Random")
if(input == "Portrait")
var/datum/portrait_picker/tgui = new(usr)//create the datum
tgui.ui_interact(usr)//datum has a tgui component, here we open the window
return "ai-portrait" //just take this until they decide
return "ai-[lowertext(input)]"
GLOBAL_LIST_INIT(security_depts_prefs, list(SEC_DEPT_RANDOM, SEC_DEPT_NONE, SEC_DEPT_ENGINEERING, SEC_DEPT_MEDICAL, SEC_DEPT_SCIENCE, SEC_DEPT_SUPPLY))
//Backpacks
@@ -277,6 +284,17 @@ GLOBAL_LIST_INIT(wisdoms, world.file2list("strings/wisdoms.txt"))
GLOBAL_LIST_INIT(speech_verbs, list("default","says","gibbers", "states", "chitters", "chimpers", "declares", "bellows", "buzzes" ,"beeps", "chirps", "clicks", "hisses" ,"poofs" , "puffs", "rattles", "mewls" ,"barks", "blorbles", "squeaks", "squawks", "flutters", "warbles", "caws", "gekkers", "clucks"))
GLOBAL_LIST_INIT(roundstart_tongues, list("default","human tongue" = /obj/item/organ/tongue, "lizard tongue" = /obj/item/organ/tongue/lizard, "skeleton tongue" = /obj/item/organ/tongue/bone, "fly tongue" = /obj/item/organ/tongue/fly, "ipc tongue" = /obj/item/organ/tongue/robot/ipc, "xeno tongue" = /obj/item/organ/tongue/alien))
/proc/get_roundstart_languages()
var/list/languages = subtypesof(/datum/language)
var/list/roundstart_languages = list("None") //default option for the list
for(var/some_language in languages)
var/datum/language/language = some_language
if(initial(language.chooseable_roundstart))
roundstart_languages[initial(language.name)] = some_language
return roundstart_languages
GLOBAL_LIST_INIT(roundstart_languages, get_roundstart_languages())
//SPECIES BODYPART LISTS
//locked parts are those that your picked species requires to have
//unlocked parts are those that anyone can choose on customisation regardless
+3
View File
@@ -48,6 +48,9 @@ GLOBAL_LIST_EMPTY_TYPED(areas_by_type, /area)
GLOBAL_LIST_EMPTY(all_abstract_markers)
/// Global list of megafauna spawns on cave gen
GLOBAL_LIST_INIT(megafauna_spawn_list, list(/mob/living/simple_animal/hostile/megafauna/dragon = 4, /mob/living/simple_animal/hostile/megafauna/colossus = 2, /mob/living/simple_animal/hostile/megafauna/bubblegum = 6))
GLOBAL_LIST_EMPTY(stationroom_landmarks) //List of all spawns for stationrooms
///Away missions, VR, random z levels stuff.
+3
View File
@@ -132,6 +132,9 @@ GLOBAL_LIST_INIT(traits_by_type, list(
"TRAIT_NODROP" = TRAIT_NODROP,
"TRAIT_NO_TELEPORT" = TRAIT_NO_TELEPORT,
"TRAIT_SPOOKY_THROW" = TRAIT_SPOOKY_THROW
),
/datum/mind = list(
"TRAIT_CLOWN_MENTALITY" = TRAIT_CLOWN_MENTALITY
)
))
+2 -2
View File
@@ -48,7 +48,7 @@
to_chat(src, "<span class='warning'>You're experiencing a bug. Reconnect immediately to fix it. Admins have been notified.</span>")
if(REALTIMEOFDAY >= chnotify + 9000)
chnotify = REALTIMEOFDAY
send2irc_adminless_only("NOCHEAT", message)
send2tgs_adminless_only("NOCHEAT", message)
return
var/list/modifiers = params2list(params)
@@ -113,7 +113,7 @@
A.AICtrlClick(src)
/mob/living/silicon/ai/AltClickOn(var/atom/A)
A.AIAltClick(src)
/*
The following criminally helpful code is just the previous code cleaned up;
+4
View File
@@ -169,5 +169,9 @@
A.attack_robot(src)
/atom/proc/attack_robot(mob/user)
if((isturf(src) || istype(src, /obj/structure/table) || istype(src, /obj/machinery/conveyor)) && get_dist(user, src) <= 1)
user.Move_Pulled(src)
return
attack_ai(user)
return
+84 -71
View File
@@ -17,21 +17,6 @@
Therefore, the top right corner (except during admin shenanigans) is at "15,15"
*/
//Lower left, persistent menu
#define ui_inventory "WEST:6,SOUTH:5"
//Middle left indicators
#define ui_lingchemdisplay "WEST,CENTER-1:15"
#define ui_lingstingdisplay "WEST:6,CENTER-3:11"
#define ui_devilsouldisplay "WEST:6,CENTER-1:15"
//Lower center, persistent menu
#define ui_sstore1 "CENTER-5:10,SOUTH:5"
#define ui_id "CENTER-4:12,SOUTH:5"
#define ui_belt "CENTER-3:14,SOUTH:5"
#define ui_back "CENTER-2:14,SOUTH:5"
/proc/ui_hand_position(i) //values based on old hand ui positions (CENTER:-/+16,SOUTH:5)
var/x_off = -(!(i % 2))
var/y_off = round((i-1) / 2)
@@ -46,35 +31,23 @@
var/y_off = round((M.held_items.len-1) / 2)
return "CENTER+[x_off]:16,SOUTH+[y_off+1]:5"
//Lower left, persistent menu
#define ui_inventory "WEST:6,SOUTH:5"
//Middle left indicators
#define ui_lingchemdisplay "WEST,CENTER-1:15"
#define ui_lingstingdisplay "WEST:6,CENTER-3:11"
#define ui_devilsouldisplay "WEST:6,CENTER-1:15"
//Lower center, persistent menu
#define ui_sstore1 "CENTER-5:10,SOUTH:5"
#define ui_id "CENTER-4:12,SOUTH:5"
#define ui_belt "CENTER-3:14,SOUTH:5"
#define ui_back "CENTER-2:14,SOUTH:5"
#define ui_storage1 "CENTER+1:18,SOUTH:5"
#define ui_storage2 "CENTER+2:20,SOUTH:5"
#define ui_borg_sensor "CENTER-3:15, SOUTH:5" //borgs
#define ui_borg_lamp "CENTER-4:15, SOUTH:5" //borgs
#define ui_borg_thrusters "CENTER-5:15, SOUTH:5" //borgs
#define ui_inv1 "CENTER-2:16,SOUTH:5" //borgs
#define ui_inv2 "CENTER-1 :16,SOUTH:5" //borgs
#define ui_inv3 "CENTER :16,SOUTH:5" //borgs
#define ui_borg_module "CENTER+1:16,SOUTH:5" //borgs
#define ui_borg_store "CENTER+2:16,SOUTH:5" //borgs
#define ui_borg_camera "CENTER+3:21,SOUTH:5" //borgs
#define ui_borg_album "CENTER+4:21,SOUTH:5" //borgs
#define ui_borg_language_menu "EAST-1:27,SOUTH+2:8" //borgs
#define ui_monkey_head "CENTER-5:13,SOUTH:5" //monkey
#define ui_monkey_mask "CENTER-4:14,SOUTH:5" //monkey
#define ui_monkey_neck "CENTER-3:15,SOUTH:5" //monkey
#define ui_monkey_back "CENTER-2:16,SOUTH:5" //monkey
//#define ui_alien_storage_l "CENTER-2:14,SOUTH:5"//alien
#define ui_alien_storage_r "CENTER+1:18,SOUTH:5"//alien
#define ui_alien_language_menu "EAST-3:26,SOUTH:5" //alien
#define ui_drone_drop "CENTER+1:18,SOUTH:5" //maintenance drones
#define ui_drone_pull "CENTER+2:2,SOUTH:5" //maintenance drones
#define ui_drone_storage "CENTER-2:14,SOUTH:5" //maintenance drones
#define ui_drone_head "CENTER-3:14,SOUTH:5" //maintenance drones
//Lower right, persistent menu
#define ui_drop_throw "EAST-1:28,SOUTH+1:7"
#define ui_pull_resist "EAST-2:26,SOUTH+1:7"
@@ -88,11 +61,6 @@
#define ui_language_menu "EAST-5:4,SOUTH:21"//CIT CHANGE - ditto
#define ui_voremode "EAST-5:20,SOUTH:5"
#define ui_borg_pull "EAST-2:26,SOUTH+1:7"
#define ui_borg_radio "EAST-1:28,SOUTH+1:7"
#define ui_borg_intents "EAST-2:26,SOUTH:5"
//Upper-middle right (alerts)
#define ui_alert1 "EAST-1:28,CENTER+5:27"
#define ui_alert2 "EAST-1:28,CENTER+4:25"
@@ -100,31 +68,70 @@
#define ui_alert4 "EAST-1:28,CENTER+2:21"
#define ui_alert5 "EAST-1:28,CENTER+1:19"
//Middle right (status indicators)
#define ui_healthdoll "EAST-1:28,CENTER-2:13"
#define ui_health "EAST-1:28,CENTER-1:15"
#define ui_internal "EAST-1:28,CENTER+1:19"//CIT CHANGE - moves internal icon up a little bit to accommodate for the stamina meter
#define ui_mood "EAST-1:28,CENTER-3:10"
// #define ui_spacesuit "EAST-1:28,CENTER-4:10"
//living
//Pop-up inventory
#define ui_shoes "WEST+1:8,SOUTH:5"
#define ui_iclothing "WEST:6,SOUTH+1:7"
#define ui_oclothing "WEST+1:8,SOUTH+1:7"
#define ui_gloves "WEST+2:10,SOUTH+1:7"
#define ui_glasses "WEST:6,SOUTH+3:11"
#define ui_mask "WEST+1:8,SOUTH+2:9"
#define ui_ears "WEST+2:10,SOUTH+2:9"
#define ui_neck "WEST:6,SOUTH+2:9"
#define ui_head "WEST+1:8,SOUTH+3:11"
//Generic living
#define ui_living_pull "EAST-1:28,CENTER-2:15"
#define ui_living_health "EAST-1:28,CENTER:15"
//borgs
#define ui_borg_health "EAST-1:28,CENTER-1:15" //borgs have the health display where humans have the pressure damage indicator.
//Monkeys
#define ui_monkey_head "CENTER-5:13,SOUTH:5"
#define ui_monkey_mask "CENTER-4:14,SOUTH:5"
#define ui_monkey_neck "CENTER-3:15,SOUTH:5"
#define ui_monkey_back "CENTER-2:16,SOUTH:5"
//aliens
#define ui_alien_health "EAST,CENTER-1:15" //aliens have the health display where humans have the pressure damage indicator.
//Drones
#define ui_drone_drop "CENTER+1:18,SOUTH:5"
#define ui_drone_pull "CENTER+2:2,SOUTH:5"
#define ui_drone_storage "CENTER-2:14,SOUTH:5"
#define ui_drone_head "CENTER-3:14,SOUTH:5"
//Cyborgs
#define ui_borg_health "EAST-1:28,CENTER-1:15"
#define ui_borg_pull "EAST-2:26,SOUTH+1:7"
#define ui_borg_radio "EAST-1:28,SOUTH+1:7"
#define ui_borg_intents "EAST-2:26,SOUTH:5"
#define ui_borg_lamp "CENTER-3:16, SOUTH:5"
#define ui_borg_tablet "CENTER-4:16, SOUTH:5"
#define ui_inv1 "CENTER-2:16,SOUTH:5"
#define ui_inv2 "CENTER-1 :16,SOUTH:5"
#define ui_inv3 "CENTER :16,SOUTH:5"
#define ui_borg_module "CENTER+1:16,SOUTH:5"
#define ui_borg_store "CENTER+2:16,SOUTH:5"
#define ui_borg_camera "CENTER+3:21,SOUTH:5"
#define ui_borg_alerts "CENTER+4:21,SOUTH:5"
#define ui_borg_language_menu "CENTER+4:21,SOUTH+1:5"
#define ui_borg_sensor "CENTER-6:16, SOUTH:5" //LEGACY
#define ui_borg_thrusters "CENTER-5:16, SOUTH:5" //LEGACY
//Aliens
#define ui_alien_health "EAST,CENTER-1:15"
#define ui_alienplasmadisplay "EAST,CENTER-2:15"
#define ui_alien_queen_finder "EAST,CENTER-3:15"
#define ui_alien_storage_r "CENTER+1:18,SOUTH:5"
#define ui_alien_language_menu "EAST-3:26,SOUTH:5"
//constructs
//Constructs
#define ui_construct_pull "EAST,CENTER-2:15"
#define ui_construct_health "EAST,CENTER:15" //same as borgs and humans
#define ui_construct_health "EAST,CENTER:15"
// AI
#define ui_ai_core "SOUTH:6,WEST"
#define ui_ai_camera_list "SOUTH:6,WEST+1"
#define ui_ai_track_with_camera "SOUTH:6,WEST+2"
@@ -143,26 +150,32 @@
#define ui_ai_multicam "SOUTH+1:6,WEST+13"
#define ui_ai_add_multicam "SOUTH+1:6,WEST+14"
//Pop-up inventory
#define ui_shoes "WEST+1:8,SOUTH:5"
#define ui_iclothing "WEST:6,SOUTH+1:7"
#define ui_oclothing "WEST+1:8,SOUTH+1:7"
#define ui_gloves "WEST+2:10,SOUTH+1:7"
#define ui_glasses "WEST:6,SOUTH+3:11"
#define ui_mask "WEST+1:8,SOUTH+2:9"
#define ui_ears "WEST+2:10,SOUTH+2:9"
#define ui_neck "WEST:6,SOUTH+2:9"
#define ui_head "WEST+1:8,SOUTH+3:11"
// pAI
// #define ui_pai_software "SOUTH:6,WEST"
// #define ui_pai_shell "SOUTH:6,WEST+1"
// #define ui_pai_chassis "SOUTH:6,WEST+2"
// #define ui_pai_rest "SOUTH:6,WEST+3"
// #define ui_pai_light "SOUTH:6,WEST+4"
// #define ui_pai_newscaster "SOUTH:6,WEST+5"
// #define ui_pai_host_monitor "SOUTH:6,WEST+6"
// #define ui_pai_crew_manifest "SOUTH:6,WEST+7"
// #define ui_pai_state_laws "SOUTH:6,WEST+8"
// #define ui_pai_pda_send "SOUTH:6,WEST+9"
// #define ui_pai_pda_log "SOUTH:6,WEST+10"
// #define ui_pai_take_picture "SOUTH:6,WEST+12"
// #define ui_pai_view_images "SOUTH:6,WEST+13"
//Ghosts
#define ui_ghost_jumptomob "SOUTH:6,CENTER-3:24"
#define ui_ghost_orbit "SOUTH:6,CENTER-2:24"
#define ui_ghost_reenter_corpse "SOUTH:6,CENTER-1:24"
#define ui_ghost_teleport "SOUTH:6,CENTER:24"
#define ui_ghost_pai "SOUTH: 6, CENTER+1:24"
#define ui_ghost_mafia "SOUTH: 6, CENTER+2:24"
#define ui_ghost_spawners "SOUTH: 6, CENTER+1:24" // LEGACY. SAME LOC AS PAI
#define ui_ghost_jumptomob "SOUTH:6,CENTER-2:24"
#define ui_ghost_orbit "SOUTH:6,CENTER-1:24"
#define ui_ghost_reenter_corpse "SOUTH:6,CENTER:24"
#define ui_ghost_teleport "SOUTH:6,CENTER+1:24"
#define ui_ghost_spawners "SOUTH: 6, CENTER+2:24"
// #define ui_wanted_lvl "NORTH,11"
//UI position overrides for 1:1 screen layout. (default is 7:5)
+4 -4
View File
@@ -3,7 +3,7 @@
var/obj/screen/blockchance
var/obj/screen/counterchance
/datum/hud/marauder/New(mob/living/simple_animal/hostile/clockwork/marauder/guardian/owner)
/datum/hud/marauder/New(mob/living/simple_animal/hostile/clockwork/guardian/owner)
..()
var/obj/screen/using
@@ -24,7 +24,7 @@
hosthealth = null
return ..()
/mob/living/simple_animal/hostile/clockwork/marauder/guardian/create_mob_hud()
/mob/living/simple_animal/hostile/clockwork/guardian/create_mob_hud()
if(client && !hud_used)
hud_used = new /datum/hud/marauder(src, ui_style2icon(client.prefs.UI_style))
@@ -37,8 +37,8 @@
desc = "Emerge or Return."
/obj/screen/marauder/emerge/Click()
if(istype(usr, /mob/living/simple_animal/hostile/clockwork/marauder/guardian))
var/mob/living/simple_animal/hostile/clockwork/marauder/guardian/G = usr
if(istype(usr, /mob/living/simple_animal/hostile/clockwork/guardian))
var/mob/living/simple_animal/hostile/clockwork/guardian/G = usr
if(G.is_in_host())
G.try_emerge()
else
+46 -21
View File
@@ -118,27 +118,7 @@
action_intent.hud = src
static_inventory += action_intent
using = new /obj/screen/mov_intent
using.icon = tg_ui_icon_to_cit_ui(ui_style) // CIT CHANGE - overrides mov intent icon
using.icon_state = (mymob.m_intent == MOVE_INTENT_RUN ? "running" : "walking")
using.screen_loc = ui_movi
using.hud = src
static_inventory += using
//CITADEL CHANGES - sprint button
using = new /obj/screen/sprintbutton
using.icon = tg_ui_icon_to_cit_ui(ui_style)
using.icon_state = ((owner.combat_flags & COMBAT_FLAG_SPRINT_ACTIVE) ? "act_sprint_on" : "act_sprint")
using.screen_loc = ui_movi
using.hud = src
static_inventory += using
//END OF CITADEL CHANGES
//same as above but buffer.
sprint_buffer = new /obj/screen/sprint_buffer
sprint_buffer.screen_loc = ui_sprintbufferloc
sprint_buffer.hud = src
static_inventory += sprint_buffer
assert_move_intent_ui(owner, TRUE)
// clickdelay
clickdelay = new
@@ -393,6 +373,51 @@
update_locked_slots()
/datum/hud/human/proc/assert_move_intent_ui(mob/living/carbon/human/owner = mymob, on_new = FALSE)
var/obj/screen/using
// delete old ones
var/list/obj/screen/victims = list()
victims += locate(/obj/screen/mov_intent) in static_inventory
victims += locate(/obj/screen/sprintbutton) in static_inventory
victims += locate(/obj/screen/sprint_buffer) in static_inventory
if(victims)
static_inventory -= victims
if(mymob?.client)
mymob.client.screen -= victims
QDEL_LIST(victims)
// make new ones
// walk/run
using = new /obj/screen/mov_intent
using.icon = tg_ui_icon_to_cit_ui(ui_style) // CIT CHANGE - overrides mov intent icon
using.screen_loc = ui_movi
using.hud = src
using.update_icon()
static_inventory += using
if(!on_new)
owner?.client?.screen += using
if(!CONFIG_GET(flag/sprint_enabled))
return
// sprint button
using = new /obj/screen/sprintbutton
using.icon = tg_ui_icon_to_cit_ui(ui_style)
using.icon_state = ((owner.combat_flags & COMBAT_FLAG_SPRINT_ACTIVE) ? "act_sprint_on" : "act_sprint")
using.screen_loc = ui_movi
using.hud = src
static_inventory += using
if(!on_new)
owner?.client?.screen += using
// same as above but buffer.
sprint_buffer = new /obj/screen/sprint_buffer
sprint_buffer.screen_loc = ui_sprintbufferloc
sprint_buffer.hud = src
static_inventory += sprint_buffer
if(!on_new)
owner?.client?.screen += using
/datum/hud/human/update_locked_slots()
if(!mymob)
return
-3
View File
@@ -38,9 +38,6 @@
/obj/screen/plane_master/proc/shadow(_size, _offset = 0, _x = 0, _y = 0, _color = "#04080FAA")
filters += filter(type = "drop_shadow", x = _x, y = _y, color = _color, size = _size, offset = _offset)
/obj/screen/plane_master/proc/clear_filters()
filters = list()
///Contains just the floor
/obj/screen/plane_master/floor
name = "floor plane master"
+112 -75
View File
@@ -68,57 +68,17 @@
var/mob/living/silicon/robot/R = usr
R.uneq_active()
/obj/screen/robot/lamp
name = "headlamp"
icon_state = "lamp0"
/obj/screen/robot/lamp/Click()
if(..())
return
var/mob/living/silicon/robot/R = usr
R.control_headlamp()
/obj/screen/robot/thrusters
name = "ion thrusters"
icon_state = "ionpulse0"
/obj/screen/robot/thrusters/Click()
if(..())
return
var/mob/living/silicon/robot/R = usr
R.toggle_ionpulse()
/obj/screen/robot/sensors
name = "Sensor Augmentation"
icon_state = "cyborg_sensor"
/obj/screen/robot/sensors/Click()
if(..())
return
var/mob/living/silicon/S = usr
S.toggle_sensors()
/obj/screen/robot/language_menu
name = "silicon language selection"
icon_state = "talk_wheel"
/obj/screen/robot/language_menu/Click()
if(..())
return
var/mob/living/silicon/S = usr
S.open_language_menu(usr)
/datum/hud/robot
ui_style = 'icons/mob/screen_cyborg.dmi'
/datum/hud/robot/New(mob/owner)
..()
var/mob/living/silicon/robot/mymobR = mymob
// i, Robit
var/mob/living/silicon/robot/robit = mymob
var/obj/screen/using
using = new/obj/screen/robot/language_menu
using = new/obj/screen/language_menu
using.screen_loc = ui_borg_language_menu
using.hud = src
static_inventory += using
//Radio
@@ -128,56 +88,72 @@
static_inventory += using
//Module select
using = new /obj/screen/robot/module1()
using.screen_loc = ui_inv1
using.hud = src
static_inventory += using
mymobR.inv1 = using
if(!robit.inv1)
robit.inv1 = new /obj/screen/robot/module1()
using = new /obj/screen/robot/module2()
using.screen_loc = ui_inv2
using.hud = src
static_inventory += using
mymobR.inv2 = using
robit.inv1.screen_loc = ui_inv1
robit.inv1.hud = src
static_inventory += robit.inv1
using = new /obj/screen/robot/module3()
using.screen_loc = ui_inv3
using.hud = src
static_inventory += using
mymobR.inv3 = using
if(!robit.inv2)
robit.inv2 = new /obj/screen/robot/module2()
robit.inv2.screen_loc = ui_inv2
robit.inv2.hud = src
static_inventory += robit.inv2
if(!robit.inv3)
robit.inv3 = new /obj/screen/robot/module3()
robit.inv3.screen_loc = ui_inv3
robit.inv3.hud = src
static_inventory += robit.inv3
//End of module select
using = new /obj/screen/robot/lamp()
using.screen_loc = ui_borg_lamp
using.hud = src
static_inventory += using
robit.lampButton = using
var/obj/screen/robot/lamp/lampscreen = using
lampscreen.robot = robit
//Photography stuff
using = new /obj/screen/ai/image_take()
using.screen_loc = ui_borg_camera
using.hud = src
static_inventory += using
using = new /obj/screen/ai/image_view()
using.screen_loc = ui_borg_album
using.hud = src
static_inventory += using
//Sec/Med HUDs
using = new /obj/screen/robot/sensors()
using.screen_loc = ui_borg_sensor
using.hud = src
static_inventory += using
//Headlamp control
using = new /obj/screen/robot/lamp()
using.screen_loc = ui_borg_lamp
//Borg Integrated Tablet
using = new /obj/screen/robot/modPC()
using.screen_loc = ui_borg_tablet
using.hud = src
static_inventory += using
robit.interfaceButton = using
if(robit.modularInterface)
using.vis_contents += robit.modularInterface
var/obj/screen/robot/modPC/tabletbutton = using
tabletbutton.robot = robit
//Alerts
using = new /obj/screen/robot/alerts()
using.screen_loc = ui_borg_alerts
using.hud = src
static_inventory += using
mymobR.lamp_button = using
//Thrusters
using = new /obj/screen/robot/thrusters()
using.screen_loc = ui_borg_thrusters
using.hud = src
static_inventory += using
mymobR.thruster_button = using
robit.thruster_button = using
//Intent
action_intent = new /obj/screen/act_intent/robot()
@@ -191,20 +167,21 @@
infodisplay += healths
//Installed Module
mymobR.hands = new /obj/screen/robot/module()
mymobR.hands.screen_loc = ui_borg_module
static_inventory += mymobR.hands
robit.hands = new /obj/screen/robot/module()
robit.hands.screen_loc = ui_borg_module
robit.hands.hud = src
static_inventory += robit.hands
//Store
module_store_icon = new /obj/screen/robot/store()
module_store_icon.hud = src
module_store_icon.screen_loc = ui_borg_store
module_store_icon.hud = src
pull_icon = new /obj/screen/pull()
pull_icon.icon = 'icons/mob/screen_cyborg.dmi'
pull_icon.screen_loc = ui_borg_pull
pull_icon.hud = src
pull_icon.update_icon()
pull_icon.screen_loc = ui_borg_pull
hotkeybuttons += pull_icon
@@ -242,13 +219,13 @@
screenmob.client.screen += module_store_icon //"store" icon
if(!R.module.modules)
to_chat(usr, "<span class='danger'>Selected module has no modules to select</span>")
to_chat(usr, "<span class='warning'>Selected module has no modules to select!</span>")
return
if(!R.robot_modules_background)
return
var/display_rows = CEILING(length(R.module.get_inactive_modules()) / 8, 1)
var/display_rows = max(CEILING(length(R.module.get_inactive_modules()) / 8, 1),1)
R.robot_modules_background.screen_loc = "CENTER-4:16,SOUTH+1:7 to CENTER+3:16,SOUTH+[display_rows]:7"
screenmob.client.screen += R.robot_modules_background
@@ -305,3 +282,63 @@
else
for(var/obj/item/I in R.held_items)
screenmob.client.screen -= I
/obj/screen/robot/lamp
name = "headlamp"
icon_state = "lamp_off"
var/mob/living/silicon/robot/robot
/obj/screen/robot/lamp/Click()
. = ..()
if(.)
return
robot?.toggle_headlamp()
update_icon()
/obj/screen/robot/lamp/update_icon()
if(robot?.lamp_enabled)
icon_state = "lamp_on"
else
icon_state = "lamp_off"
/obj/screen/robot/alerts
name = "Alert Panel"
icon = 'icons/mob/screen_ai.dmi'
icon_state = "alerts"
/obj/screen/robot/alerts/Click()
. = ..()
if(.)
return
var/mob/living/silicon/robot/borgo = usr
borgo.robot_alerts()
/obj/screen/robot/thrusters
name = "ion thrusters"
icon_state = "ionpulse0"
/obj/screen/robot/thrusters/Click()
if(..())
return
var/mob/living/silicon/robot/R = usr
R.toggle_ionpulse()
/obj/screen/robot/sensors
name = "Sensor Augmentation"
icon_state = "cyborg_sensor"
/obj/screen/robot/sensors/Click()
if(..())
return
var/mob/living/silicon/S = usr
S.toggle_sensors()
/obj/screen/robot/modPC
name = "Modular Interface"
icon_state = "template"
var/mob/living/silicon/robot/robot
/obj/screen/robot/modPC/Click()
. = ..()
if(.)
return
robot.modularInterface?.interact(robot)
+6 -2
View File
@@ -351,15 +351,19 @@
icon = 'icons/mob/screen_midnight.dmi'
icon_state = "running"
/obj/screen/mov_intent/Initialize(mapload)
. = ..()
update_icon()
/obj/screen/mov_intent/Click()
toggle(usr)
/obj/screen/mov_intent/update_icon_state()
switch(hud?.mymob?.m_intent)
if(MOVE_INTENT_WALK)
icon_state = "walking"
icon_state = CONFIG_GET(flag/sprint_enabled)? "walking" : "walking_nosprint"
if(MOVE_INTENT_RUN)
icon_state = "running"
icon_state = CONFIG_GET(flag/sprint_enabled)? "running" : "running_nosprint"
/obj/screen/mov_intent/proc/toggle(mob/user)
if(isobserver(user))
+47 -9
View File
@@ -85,21 +85,16 @@
makeItemInactive()
/obj/screen/storage/volumetric_box/proc/makeItemInactive()
if(!our_item)
return
our_item.layer = VOLUMETRIC_STORAGE_ITEM_LAYER
our_item.plane = VOLUMETRIC_STORAGE_ITEM_PLANE
return
/obj/screen/storage/volumetric_box/proc/makeItemActive()
if(!our_item)
return
our_item.layer = VOLUMETRIC_STORAGE_ACTIVE_ITEM_LAYER //make sure we display infront of the others!
our_item.plane = VOLUMETRIC_STORAGE_ACTIVE_ITEM_PLANE
return
/obj/screen/storage/volumetric_box/center
icon_state = "stored_continue"
var/obj/screen/storage/volumetric_edge/stored_left/left
var/obj/screen/storage/volumetric_edge/stored_right/right
var/obj/screen/storage/item_holder/holder
var/pixel_size
/obj/screen/storage/volumetric_box/center/Initialize(mapload, new_master, our_item)
@@ -110,6 +105,9 @@
/obj/screen/storage/volumetric_box/center/Destroy()
QDEL_NULL(left)
QDEL_NULL(right)
vis_contents.Cut()
if(holder)
QDEL_NULL(holder)
return ..()
/obj/screen/storage/volumetric_box/center/proc/on_screen_objects()
@@ -123,13 +121,36 @@
return
pixel_size = pixels
cut_overlays()
vis_contents.Cut()
//our icon size is 32 pixels.
transform = matrix((pixels - (VOLUMETRIC_STORAGE_BOX_BORDER_SIZE * 2)) / VOLUMETRIC_STORAGE_BOX_ICON_SIZE, 0, 0, 0, 1, 0)
var/multiplier = (pixels - (VOLUMETRIC_STORAGE_BOX_BORDER_SIZE * 2)) / VOLUMETRIC_STORAGE_BOX_ICON_SIZE
transform = matrix(multiplier, 0, 0, 0, 1, 0)
if(our_item)
if(holder)
qdel(holder)
holder = new(null, src, our_item)
holder.transform = matrix(1 / multiplier, 0, 0, 0, 1, 0)
holder.mouse_opacity = MOUSE_OPACITY_TRANSPARENT
holder.appearance_flags &= ~RESET_TRANSFORM
makeItemInactive()
vis_contents += holder
left.pixel_x = -((pixels - VOLUMETRIC_STORAGE_BOX_ICON_SIZE) * 0.5) - VOLUMETRIC_STORAGE_BOX_BORDER_SIZE
right.pixel_x = ((pixels - VOLUMETRIC_STORAGE_BOX_ICON_SIZE) * 0.5) + VOLUMETRIC_STORAGE_BOX_BORDER_SIZE
add_overlay(left)
add_overlay(right)
/obj/screen/storage/volumetric_box/center/makeItemInactive()
if(!holder)
return
holder.layer = VOLUMETRIC_STORAGE_ITEM_LAYER
holder.plane = VOLUMETRIC_STORAGE_ITEM_PLANE
/obj/screen/storage/volumetric_box/center/makeItemActive()
if(!holder)
return
holder.our_item.layer = VOLUMETRIC_STORAGE_ACTIVE_ITEM_LAYER //make sure we display infront of the others!
holder.our_item.plane = VOLUMETRIC_STORAGE_ACTIVE_ITEM_PLANE
/obj/screen/storage/volumetric_edge
layer = VOLUMETRIC_STORAGE_BOX_LAYER
plane = VOLUMETRIC_STORAGE_BOX_PLANE
@@ -157,3 +178,20 @@
/obj/screen/storage/volumetric_edge/stored_right
icon_state = "stored_end"
appearance_flags = APPEARANCE_UI | KEEP_APART | RESET_TRANSFORM
/obj/screen/storage/item_holder
var/obj/item/our_item
vis_flags = NONE
/obj/screen/storage/item_holder/Initialize(mapload, new_master, obj/item/I)
. = ..()
our_item = I
vis_contents += I
/obj/screen/storage/item_holder/Destroy()
vis_contents.Cut()
our_item = null
return ..()
/obj/screen/storage/item_holder/Click(location, control, params)
return our_item.Click(location, control, params)
+3
View File
@@ -97,6 +97,9 @@
M.lastattacker = user.real_name
M.lastattackerckey = user.ckey
if(force && M == user && user.client)
user.client.give_award(/datum/award/achievement/misc/selfouch, user)
user.do_attack_animation(M)
M.attacked_by(src, user, attackchain_flags, damage_multiplier)
@@ -22,7 +22,7 @@
var/motd
// var/policy
// var/static/regex/ic_filter_regex
var/static/regex/ic_filter_regex
/datum/controller/configuration/proc/admin_reload()
if(IsAdminAdvancedProcCall())
@@ -53,7 +53,7 @@
loadmaplist(CONFIG_MAPS_FILE)
LoadMOTD()
// LoadPolicy()
// LoadChatFilter()
LoadChatFilter()
if (Master)
Master.OnConfigLoad()
@@ -404,9 +404,9 @@ Example config:
if(!Get(/datum/config_entry/flag/no_storyteller_threat_removal))
var/min_chaos = (probabilities in storyteller_min_chaos) ? storyteller_min_chaos[config_tag] : initial(S.min_chaos)
var/max_chaos = (probabilities in storyteller_max_chaos) ? storyteller_max_chaos[config_tag] : initial(S.max_chaos)
if(SSpersistence.average_dynamic_threat < min_chaos)
if(SSpersistence.average_threat + 50 < min_chaos)
continue
if(SSpersistence.average_dynamic_threat > max_chaos)
if(SSpersistence.average_threat + 50 > max_chaos)
continue
if(SSpersistence.saved_storytellers.len == repeated_mode_adjust.len)
var/name = initial(S.name)
@@ -415,7 +415,7 @@ Example config:
while(recent_round)
adjustment += repeated_mode_adjust[recent_round]
recent_round = SSpersistence.saved_modes.Find(name,recent_round+1,0)
probability *= ((100-adjustment)/100)
probability *= max(0,((100-adjustment)/100))
runnable_storytellers[S] = probability
return runnable_storytellers
@@ -425,6 +425,7 @@ Example config:
var/list/min_pop = Get(/datum/config_entry/keyed_list/min_pop)
var/list/max_pop = Get(/datum/config_entry/keyed_list/max_pop)
var/list/repeated_mode_adjust = Get(/datum/config_entry/number_list/repeated_mode_adjust)
var/desired_chaos_level = 9 - SSpersistence.get_recent_chaos()
for(var/T in gamemode_cache)
var/datum/game_mode/M = new T()
if(!(M.config_tag in modes))
@@ -448,7 +449,18 @@ Example config:
while(recent_round)
adjustment += repeated_mode_adjust[recent_round]
recent_round = SSpersistence.saved_modes.Find(M.config_tag,recent_round+1,0)
final_weight *= ((100-adjustment)/100)
final_weight *= max(0,((100-adjustment)/100))
if(Get(/datum/config_entry/flag/weigh_by_recent_chaos))
var/chaos_level = M.get_chaos()
var/exponent = Get(/datum/config_entry/number/chaos_exponent)
var/delta = chaos_level - desired_chaos_level
if(desired_chaos_level > 5)
delta = abs(min(delta, 0))
else if(desired_chaos_level < 5)
delta = max(delta, 0)
else
delta = abs(delta)
final_weight /= (delta + 1) ** exponent
runnable_modes[M] = final_weight
return runnable_modes
@@ -474,7 +486,7 @@ Example config:
continue
runnable_modes[M] = probabilities[M.config_tag]
return runnable_modes
/*
/datum/controller/configuration/proc/LoadChatFilter()
var/list/in_character_filter = list()
if(!fexists("[directory]/in_character_filter.txt"))
@@ -487,7 +499,7 @@ Example config:
continue
in_character_filter += REGEX_QUOTE(line)
ic_filter_regex = in_character_filter.len ? regex("\\b([jointext(in_character_filter, "|")])\\b", "i") : null
*/
//Message admins when you can.
/datum/controller/configuration/proc/DelayedMessageAdmins(text)
addtimer(CALLBACK(GLOBAL_PROC, /proc/message_admins, text), 0)
@@ -22,11 +22,10 @@
/datum/config_entry/string/cross_comms_name
/datum/config_entry/string/medal_hub_address
/datum/config_entry/string/medal_hub_password
protection = CONFIG_ENTRY_HIDDEN
/datum/config_entry/string/cross_comms_network
protection = CONFIG_ENTRY_LOCKED
/// cit config
/datum/config_entry/keyed_list/cross_server_bunker_override
key_mode = KEY_MODE_TEXT
value_mode = VALUE_MODE_TEXT
@@ -7,6 +7,13 @@
/datum/config_entry/keyed_list/probability/ValidateListEntry(key_name)
return key_name in config.modes
/datum/config_entry/keyed_list/chaos_level
key_mode = KEY_MODE_TEXT
value_mode = VALUE_MODE_NUM
/datum/config_entry/keyed_list/chaos_level/ValidateListEntry(key_name)
return key_name in config.modes
/datum/config_entry/keyed_list/max_pop
key_mode = KEY_MODE_TEXT
value_mode = VALUE_MODE_NUM
@@ -290,6 +297,17 @@
var/datum/movespeed_modifier/config_walk_run/M = get_cached_movespeed_modifier(/datum/movespeed_modifier/config_walk_run/walk)
M.sync()
/datum/config_entry/flag/sprint_enabled
config_entry_value = TRUE
/datum/config_entry/flag/sprint_enabled/ValidateAndSet(str_val)
. = ..()
for(var/datum/hud/human/H)
H.assert_move_intent_ui()
if(!config_entry_value) // disabled
for(var/mob/living/L in world)
L.disable_intentional_sprint_mode()
/datum/config_entry/number/movedelay/sprint_speed_increase
config_entry_value = 1
@@ -484,6 +502,8 @@
/datum/config_entry/flag/modetier_voting
/datum/config_entry/flag/must_be_readied_to_vote_gamemode
/datum/config_entry/number/dropped_modes
config_entry_value = 3
@@ -583,3 +603,8 @@
/// Dirtyness multiplier for making turfs dirty
/datum/config_entry/number/turf_dirty_multiplier
config_entry_value = 1
/datum/config_entry/flag/weigh_by_recent_chaos
/datum/config_entry/number/chaos_exponent
config_entry_value = 1
@@ -26,50 +26,6 @@
/datum/config_entry/flag/hub // if the game appears on the hub or not
/datum/config_entry/flag/log_ooc // log OOC channel
/datum/config_entry/flag/log_access // log login/logout
/datum/config_entry/flag/log_say // log client say
/datum/config_entry/flag/log_admin // log admin actions
protection = CONFIG_ENTRY_LOCKED
/datum/config_entry/flag/log_prayer // log prayers
/datum/config_entry/flag/log_law // log lawchanges
/datum/config_entry/flag/log_game // log game events
/datum/config_entry/flag/log_virus // log virology data
/datum/config_entry/flag/log_vote // log voting
/datum/config_entry/flag/log_craft // log crafting
/datum/config_entry/flag/log_whisper // log client whisper
/datum/config_entry/flag/log_attack // log attack messages
/datum/config_entry/flag/log_emote // log emotes
/datum/config_entry/flag/log_adminchat // log admin chat messages
protection = CONFIG_ENTRY_LOCKED
/datum/config_entry/flag/log_shuttle // log shuttle related actions, ie shuttle computers, shuttle manipulator, emergency console
/datum/config_entry/flag/log_pda // log pda messages
/datum/config_entry/flag/log_telecomms // log telecomms messages
/datum/config_entry/flag/log_twitter // log certain expliotable parrots and other such fun things in a JSON file of twitter valid phrases.
/datum/config_entry/flag/log_world_topic // log all world.Topic() calls
/datum/config_entry/flag/log_manifest // log crew manifest to seperate file
/datum/config_entry/flag/log_job_debug // log roundstart divide occupations debug information to a file
/datum/config_entry/flag/allow_admin_ooccolor // Allows admins with relevant permissions to have their own ooc colour
/datum/config_entry/flag/allow_vote_restart // allow votes to restart
@@ -319,6 +275,11 @@
/datum/config_entry/flag/panic_bunker // prevents people the server hasn't seen before from connecting
/datum/config_entry/number/panic_bunker_living // living time in minutes that a player needs to pass the panic bunker
/datum/config_entry/string/panic_bunker_message
config_entry_value = "Sorry but the server is currently not accepting connections from never before seen players."
/datum/config_entry/number/notify_new_player_age // how long do we notify admins of a new player
min_val = -1
@@ -472,10 +433,6 @@
/datum/config_entry/string/default_view_square
config_entry_value = "15x15"
/datum/config_entry/flag/log_pictures
/datum/config_entry/flag/picture_logging_camera
/datum/config_entry/number/max_bunker_days
config_entry_value = 7
min_val = 1
@@ -0,0 +1,70 @@
/datum/config_entry/flag/log_ooc // log OOC channel
config_entry_value = TRUE
/datum/config_entry/flag/log_access // log login/logout
config_entry_value = TRUE
/datum/config_entry/flag/log_say // log client say
config_entry_value = TRUE
/datum/config_entry/flag/log_admin // log admin actions
protection = CONFIG_ENTRY_LOCKED
/datum/config_entry/flag/log_prayer // log prayers
config_entry_value = TRUE
/datum/config_entry/flag/log_law // log lawchanges
config_entry_value = TRUE
/datum/config_entry/flag/log_game // log game events
config_entry_value = TRUE
/datum/config_entry/flag/log_virus // log virology data
config_entry_value = TRUE
/datum/config_entry/flag/log_vote // log voting
config_entry_value = TRUE
/datum/config_entry/flag/log_craft // log crafting
config_entry_value = TRUE
/datum/config_entry/flag/log_whisper // log client whisper
config_entry_value = TRUE
/datum/config_entry/flag/log_attack // log attack messages
config_entry_value = TRUE
/datum/config_entry/flag/log_emote // log emotes
config_entry_value = TRUE
/datum/config_entry/flag/log_adminchat // log admin chat messages
protection = CONFIG_ENTRY_LOCKED
/datum/config_entry/flag/log_shuttle // log shuttle related actions, ie shuttle computers, shuttle manipulator, emergency console
config_entry_value = TRUE
/datum/config_entry/flag/log_pda // log pda messages
config_entry_value = TRUE
/datum/config_entry/flag/log_telecomms // log telecomms messages
config_entry_value = TRUE
/datum/config_entry/flag/log_twitter // log certain expliotable parrots and other such fun things in a JSON file of twitter valid phrases.
config_entry_value = TRUE
/datum/config_entry/flag/log_world_topic // log all world.Topic() calls
config_entry_value = TRUE
/datum/config_entry/flag/log_manifest // log crew manifest to seperate file
config_entry_value = TRUE
/datum/config_entry/flag/log_job_debug // log roundstart divide occupations debug information to a file
config_entry_value = TRUE
/datum/config_entry/flag/log_pictures
/datum/config_entry/flag/picture_logging_camera
/// forces log_href for tgui
/datum/config_entry/flag/emergency_tgui_logging
config_entry_value = FALSE
@@ -0,0 +1,74 @@
SUBSYSTEM_DEF(achievements)
name = "Achievements"
flags = SS_NO_FIRE
init_order = INIT_ORDER_ACHIEVEMENTS
var/achievements_enabled = FALSE
///List of achievements
var/list/datum/award/achievement/achievements = list()
///List of scores
var/list/datum/award/score/scores = list()
///List of all awards
var/list/datum/award/awards = list()
/datum/controller/subsystem/achievements/Initialize(timeofday)
if(!SSdbcore.Connect())
return ..()
achievements_enabled = TRUE
for(var/T in subtypesof(/datum/award/achievement))
var/instance = new T
achievements[T] = instance
awards[T] = instance
for(var/T in subtypesof(/datum/award/score))
var/instance = new T
scores[T] = instance
awards[T] = instance
update_metadata()
for(var/i in GLOB.clients)
var/client/C = i
if(!C.player_details.achievements.initialized)
C.player_details.achievements.InitializeData()
return ..()
/datum/controller/subsystem/achievements/Shutdown()
save_achievements_to_db()
/datum/controller/subsystem/achievements/proc/save_achievements_to_db()
var/list/cheevos_to_save = list()
for(var/ckey in GLOB.player_details)
var/datum/player_details/PD = GLOB.player_details[ckey]
if(!PD || !PD.achievements)
continue
cheevos_to_save += PD.achievements.get_changed_data()
if(!length(cheevos_to_save))
return
SSdbcore.MassInsert(format_table_name("achievements"),cheevos_to_save,duplicate_key = TRUE)
//Update the metadata if any are behind
/datum/controller/subsystem/achievements/proc/update_metadata()
var/list/current_metadata = list()
//select metadata here
var/datum/db_query/Q = SSdbcore.NewQuery("SELECT achievement_key,achievement_version FROM [format_table_name("achievement_metadata")]")
if(!Q.Execute(async = TRUE))
qdel(Q)
return
else
while(Q.NextRow())
current_metadata[Q.item[1]] = text2num(Q.item[2])
qdel(Q)
var/list/to_update = list()
for(var/T in awards)
var/datum/award/A = awards[T]
if(!A.database_id)
continue
if(!current_metadata[A.database_id] || current_metadata[A.database_id] < A.achievement_version)
to_update += list(A.get_metadata_row())
if(to_update.len)
SSdbcore.MassInsert(format_table_name("achievement_metadata"),to_update,duplicate_key = TRUE)
+75
View File
@@ -0,0 +1,75 @@
SUBSYSTEM_DEF(activity)
name = "Activity tracking"
flags = SS_BACKGROUND | SS_NO_TICK_CHECK
priority = FIRE_PRIORITY_ACTIVITY
wait = 1 MINUTES
var/list/deferred_threats = list()
var/current_threat = 0
var/list/threat_history = list()
var/list/threats = list()
/datum/controller/subsystem/activity/Initialize(timeofday)
RegisterSignal(SSdcs,COMSIG_GLOB_EXPLOSION,.proc/on_explosion)
RegisterSignal(SSdcs,COMSIG_GLOB_MOB_DEATH,.proc/on_death)
/datum/controller/subsystem/activity/fire(resumed = 0)
calculate_threat()
/datum/controller/subsystem/activity/proc/calculate_threat()
threats = deferred_threats.Copy()
deferred_threats.Cut()
threats["antagonists"] = 0
for(var/datum/antagonist/A in GLOB.antagonists)
if(A?.owner?.current && A.owner.current.stat != DEAD)
threats["antagonists"] += A.threat()
threats["events"] = 0
for(var/r in SSevents.running)
var/datum/round_event/R = r
threats["events"] += R.threat()
threats["players"] = 0
SEND_SIGNAL(src, COMSIG_THREAT_CALC, threats)
for(var/m in GLOB.player_list)
var/mob/M = m
if (M?.mind?.assigned_role && M.stat != DEAD)
var/datum/job/J = SSjob.GetJob(M.mind.assigned_role)
if(J)
if(length(M.mind.antag_datums))
threats["players"] += J.GetThreat()
else
threats["players"] -= J.GetThreat()
else if(M?.stat == DEAD && !M.voluntary_ghosted)
threats["dead_players"] += 1
current_threat = 0
for(var/threat_type in threats)
current_threat += threats[threat_type]
threat_history += "[world.time]"
threat_history["[world.time]"] = current_threat
/datum/controller/subsystem/activity/proc/get_average_threat()
if(!length(threat_history))
return 0
var/total_weight = 0
var/total_amt = 0
for(var/i in 1 to threat_history.len-1)
var/weight = (text2num(threat_history[i+1])-text2num(threat_history[i]))
total_weight += weight
total_amt += weight * (threat_history[threat_history[i]])
return round(total_amt / total_weight,0.1)
/datum/controller/subsystem/activity/proc/get_max_threat()
. = 0
for(var/threat in threat_history)
. = max(threat_history[threat], .)
/datum/controller/subsystem/activity/proc/on_explosion(atom/epicenter, devastation_range, heavy_impact_range, light_impact_range, took, orig_dev_range, orig_heavy_range, orig_light_range)
if(!("explosions" in deferred_threats))
deferred_threats["explosions"] = 0
var/area/A = get_area(epicenter)
if(is_station_level(epicenter.z) && (A.area_flags & BLOBS_ALLOWED) && !istype(A, /area/asteroid))
deferred_threats["explosions"] += devastation_range**2 + heavy_impact_range**2 / 4 + light_impact_range**2 / 8 // 75 for a maxcap
/datum/controller/subsystem/activity/proc/on_death(mob/M, gibbed)
if(!("crew_deaths" in deferred_threats))
deferred_threats["crew_deaths"] = 0
if(M?.mind && SSjob.GetJob(M.mind.assigned_role))
deferred_threats["crew_deaths"] += 1
+58 -61
View File
@@ -5,10 +5,10 @@ SUBSYSTEM_DEF(blackbox)
runlevels = RUNLEVEL_GAME | RUNLEVEL_POSTGAME
init_order = INIT_ORDER_BLACKBOX
var/list/feedback = list() //list of datum/feedback_variable
var/list/feedback = list() //list of datum/feedback_variable
var/list/first_death = list() //the first death of this round, assoc. vars keep track of different things
var/triggertime = 0
var/sealed = FALSE //time to stop tracking stats?
var/sealed = FALSE //time to stop tracking stats?
var/list/versions = list("antagonists" = 3,
"admin_secrets_fun_used" = 2,
"explosion" = 2,
@@ -28,12 +28,12 @@ SUBSYSTEM_DEF(blackbox)
//poll population
/datum/controller/subsystem/blackbox/fire()
set waitfor = FALSE //for population query
set waitfor = FALSE //for population query
CheckPlayerCount()
if(CONFIG_GET(flag/use_exp_tracking))
if((triggertime < 0) || (world.time > (triggertime +3000))) //subsystem fires once at roundstart then once every 10 minutes. a 5 min check skips the first fire. The <0 is midnight rollover check
if((triggertime < 0) || (world.time > (triggertime +3000))) //subsystem fires once at roundstart then once every 10 minutes. a 5 min check skips the first fire. The <0 is midnight rollover check
update_exp(10,FALSE)
/datum/controller/subsystem/blackbox/proc/CheckPlayerCount()
@@ -43,7 +43,17 @@ SUBSYSTEM_DEF(blackbox)
return
var/playercount = LAZYLEN(GLOB.player_list)
var/admincount = GLOB.admins.len
var/datum/DBQuery/query_record_playercount = SSdbcore.NewQuery("INSERT INTO [format_table_name("legacy_population")] (playercount, admincount, time, server_ip, server_port, round_id) VALUES ([playercount], [admincount], '[SQLtime()]', INET_ATON(IF('[world.internet_address]' LIKE '', '0', '[world.internet_address]')), '[world.port]', '[GLOB.round_id]')")
var/datum/db_query/query_record_playercount = SSdbcore.NewQuery({"
INSERT INTO [format_table_name("legacy_population")] (playercount, admincount, time, server_ip, server_port, round_id)
VALUES (:playercount, :admincount, :time, INET_ATON(:server_ip), :server_port, :round_id)
"}, list(
"playercount" = playercount,
"admincount" = admincount,
"time" = SQLtime(),
"server_ip" = world.internet_address || "0",
"server_port" = "[world.port]",
"round_id" = GLOB.round_id,
))
query_record_playercount.Execute()
qdel(query_record_playercount)
@@ -87,24 +97,23 @@ SUBSYSTEM_DEF(blackbox)
if (!SSdbcore.Connect())
return
// var/list/special_columns = list(
// "datetime" = "NOW()"
// )
var/list/special_columns = list(
"datetime" = "NOW()"
)
var/list/sqlrowlist = list()
for (var/datum/feedback_variable/FV in feedback)
sqlrowlist += list(list(
"datetime" = "Now()", //legacy
"round_id" = GLOB.round_id,
"key_name" = sanitizeSQL(FV.key),
"key_name" = FV.key,
"key_type" = FV.key_type,
"version" = versions[FV.key] || 1,
"json" = sanitizeSQL(json_encode(FV.json))
"json" = json_encode(FV.json)
))
if (!length(sqlrowlist))
return
SSdbcore.MassInsert(format_table_name("feedback"), sqlrowlist, ignore_errors = TRUE, delayed = TRUE)//, special_columns = special_columns)
SSdbcore.MassInsert(format_table_name("feedback"), sqlrowlist, ignore_errors = TRUE, delayed = TRUE, special_columns = special_columns)
/datum/controller/subsystem/blackbox/proc/Seal()
if(sealed)
@@ -162,13 +171,13 @@ feedback data can be recorded in 5 formats:
used for simple single-string records i.e. the current map
further calls to the same key will append saved data unless the overwrite argument is true or it already exists
when encoded calls made with overwrite will lack square brackets
calls: SSblackbox.record_feedback("text", "example", 1, "sample text")
calls: SSblackbox.record_feedback("text", "example", 1, "sample text")
SSblackbox.record_feedback("text", "example", 1, "other text")
json: {"data":["sample text","other text"]}
"amount"
used to record simple counts of data i.e. the number of ahelps received
further calls to the same key will add or subtract (if increment argument is a negative) from the saved amount
calls: SSblackbox.record_feedback("amount", "example", 8)
calls: SSblackbox.record_feedback("amount", "example", 8)
SSblackbox.record_feedback("amount", "example", 2)
json: {"data":10}
"tally"
@@ -176,7 +185,7 @@ feedback data can be recorded in 5 formats:
further calls to the same key will:
add or subtract from the saved value of the data key if it already exists
append the key and it's value if it doesn't exist
calls: SSblackbox.record_feedback("tally", "example", 1, "sample data")
calls: SSblackbox.record_feedback("tally", "example", 1, "sample data")
SSblackbox.record_feedback("tally", "example", 4, "sample data")
SSblackbox.record_feedback("tally", "example", 2, "other data")
json: {"data":{"sample data":5,"other data":2}}
@@ -188,19 +197,19 @@ feedback data can be recorded in 5 formats:
further calls to the same key will:
add or subtract from the saved value of the data key if it already exists in the same multi-dimensional position
append the key and it's value if it doesn't exist
calls: SSblackbox.record_feedback("nested tally", "example", 1, list("fruit", "orange", "apricot"))
calls: SSblackbox.record_feedback("nested tally", "example", 1, list("fruit", "orange", "apricot"))
SSblackbox.record_feedback("nested tally", "example", 2, list("fruit", "orange", "orange"))
SSblackbox.record_feedback("nested tally", "example", 3, list("fruit", "orange", "apricot"))
SSblackbox.record_feedback("nested tally", "example", 10, list("fruit", "red", "apple"))
SSblackbox.record_feedback("nested tally", "example", 1, list("vegetable", "orange", "carrot"))
json: {"data":{"fruit":{"orange":{"apricot":4,"orange":2},"red":{"apple":10}},"vegetable":{"orange":{"carrot":1}}}}
tracking values associated with a number can't merge with a nesting value, trying to do so will append the list
call: SSblackbox.record_feedback("nested tally", "example", 3, list("fruit", "orange"))
call: SSblackbox.record_feedback("nested tally", "example", 3, list("fruit", "orange"))
json: {"data":{"fruit":{"orange":{"apricot":4,"orange":2},"red":{"apple":10},"orange":3},"vegetable":{"orange":{"carrot":1}}}}
"associative"
used to record text that's associated with a value i.e. coordinates
further calls to the same key will append a new list to existing data
calls: SSblackbox.record_feedback("associative", "example", 1, list("text" = "example", "path" = /obj/item, "number" = 4))
calls: SSblackbox.record_feedback("associative", "example", 1, list("text" = "example", "path" = /obj/item, "number" = 4))
SSblackbox.record_feedback("associative", "example", 1, list("number" = 7, "text" = "example", "other text" = "sample"))
json: {"data":{"1":{"text":"example","path":"/obj/item","number":"4"},"2":{"number":"7","text":"example","other text":"sample"}}}
@@ -275,7 +284,7 @@ Versioning
/datum/feedback_variable/New(new_key, new_key_type)
key = new_key
key_type = new_key_type
/*
/datum/controller/subsystem/blackbox/proc/LogAhelp(ticket, action, message, recipient, sender)
if(!SSdbcore.Connect())
return
@@ -286,7 +295,7 @@ Versioning
"}, list("ticket" = ticket, "action" = action, "message" = message, "recipient" = recipient, "sender" = sender, "server_ip" = world.internet_address || "0", "server_port" = world.port, "round_id" = GLOB.round_id, "time" = SQLtime()))
query_log_ahelp.Execute()
qdel(query_log_ahelp)
*/
/datum/controller/subsystem/blackbox/proc/ReportDeath(mob/living/L)
set waitfor = FALSE
@@ -302,51 +311,39 @@ Versioning
first_death["area"] = "[AREACOORD(L)]"
first_death["damage"] = "<font color='#FF5555'>[L.getBruteLoss()]</font>/<font color='orange'>[L.getFireLoss()]</font>/<font color='lightgreen'>[L.getToxLoss()]</font>/<font color='lightblue'>[L.getOxyLoss()]</font>/<font color='pink'>[L.getCloneLoss()]</font>"
first_death["last_words"] = L.last_words
var/sqlname = L.real_name
var/sqlkey = L.ckey
var/sqljob = L.mind.assigned_role
var/sqlspecial = L.mind.special_role
var/sqlpod = get_area_name(L, TRUE)
var/laname = L.lastattacker
var/lakey = L.lastattackerckey
var/sqlbrute = L.getBruteLoss()
var/sqlfire = L.getFireLoss()
var/sqlbrain = L.getOrganLoss(ORGAN_SLOT_BRAIN)
var/sqloxy = L.getOxyLoss()
var/sqltox = L.getToxLoss()
var/sqlclone = L.getCloneLoss()
var/sqlstamina = L.getStaminaLoss()
var/x_coord = L.x
var/y_coord = L.y
var/z_coord = L.z
var/last_words = L.last_words
var/suicide = L.suiciding
var/map = SSmapping.config.map_name
if(!SSdbcore.Connect())
return
sqlname = sanitizeSQL(sqlname)
sqlkey = sanitizeSQL(sqlkey)
sqljob = sanitizeSQL(sqljob)
sqlspecial = sanitizeSQL(sqlspecial)
sqlpod = sanitizeSQL(sqlpod)
laname = sanitizeSQL(laname)
lakey = sanitizeSQL(lakey)
sqlbrute = sanitizeSQL(sqlbrute)
sqlfire = sanitizeSQL(sqlfire)
sqlbrain = sanitizeSQL(sqlbrain)
sqloxy = sanitizeSQL(sqloxy)
sqltox = sanitizeSQL(sqltox)
sqlclone = sanitizeSQL(sqlclone)
sqlstamina = sanitizeSQL(sqlstamina)
x_coord = sanitizeSQL(x_coord)
y_coord = sanitizeSQL(y_coord)
z_coord = sanitizeSQL(z_coord)
last_words = sanitizeSQL(last_words)
suicide = sanitizeSQL(suicide)
map = sanitizeSQL(map)
var/datum/DBQuery/query_report_death = SSdbcore.NewQuery("INSERT INTO [format_table_name("death")] (pod, x_coord, y_coord, z_coord, mapname, server_ip, server_port, round_id, tod, job, special, name, byondkey, laname, lakey, bruteloss, fireloss, brainloss, oxyloss, toxloss, cloneloss, staminaloss, last_words, suicide) VALUES ('[sqlpod]', '[x_coord]', '[y_coord]', '[z_coord]', '[map]', INET_ATON(IF('[world.internet_address]' LIKE '', '0', '[world.internet_address]')), '[world.port]', [GLOB.round_id], '[SQLtime()]', '[sqljob]', '[sqlspecial]', '[sqlname]', '[sqlkey]', '[laname]', '[lakey]', [sqlbrute], [sqlfire], [sqlbrain], [sqloxy], [sqltox], [sqlclone], [sqlstamina], '[last_words]', [suicide])")
var/datum/db_query/query_report_death = SSdbcore.NewQuery({"
INSERT INTO [format_table_name("death")] (pod, x_coord, y_coord, z_coord, mapname, server_ip, server_port, round_id, tod, job, special, name, byondkey, laname, lakey, bruteloss, fireloss, brainloss, oxyloss, toxloss, cloneloss, staminaloss, last_words, suicide)
VALUES (:pod, :x_coord, :y_coord, :z_coord, :map, INET_ATON(:internet_address), :port, :round_id, :time, :job, :special, :name, :key, :laname, :lakey, :brute, :fire, :brain, :oxy, :tox, :clone, :stamina, :last_words, :suicide)
"}, list(
"name" = L.real_name,
"key" = L.ckey,
"job" = L.mind.assigned_role,
"special" = L.mind.special_role,
"pod" = get_area_name(L, TRUE),
"laname" = L.lastattacker,
"lakey" = L.lastattackerckey,
"brute" = L.getBruteLoss(),
"fire" = L.getFireLoss(),
"brain" = L.getOrganLoss(ORGAN_SLOT_BRAIN) || BRAIN_DAMAGE_DEATH, //getOrganLoss returns null without a brain but a value is required for this column
"oxy" = L.getOxyLoss(),
"tox" = L.getToxLoss(),
"clone" = L.getCloneLoss(),
"stamina" = L.getStaminaLoss(),
"x_coord" = L.x,
"y_coord" = L.y,
"z_coord" = L.z,
"last_words" = L.last_words,
"suicide" = L.suiciding,
"map" = SSmapping.config.map_name,
"internet_address" = world.internet_address || "0",
"port" = "[world.port]",
"round_id" = GLOB.round_id,
"time" = SQLtime(),
))
if(query_report_death)
query_report_death.Execute(async = TRUE)
qdel(query_report_death)
+89
View File
@@ -0,0 +1,89 @@
SUBSYSTEM_DEF(blackmarket)
name = "Blackmarket"
flags = SS_BACKGROUND
init_order = INIT_ORDER_DEFAULT
// Descriptions for each shipping method.
var/shipping_method_descriptions = list(
SHIPPING_METHOD_LAUNCH="Launches the item at the station from space, cheap but you might not recieve your item at all.",
SHIPPING_METHOD_LTSRBT="Long-To-Short-Range-Bluespace-Transceiver, a machine that recieves items outside the station and then teleports them to the location of the uplink.",
SHIPPING_METHOD_TELEPORT="Teleports the item in a random area in the station, you get 60 seconds to get there first though."
)
var/list/datum/blackmarket_market/markets = list() // List of all existing markets.
var/list/obj/machinery/ltsrbt/telepads = list() // List of existing ltsrbts.
var/list/queued_purchases = list() // Currently queued purchases.
/datum/controller/subsystem/blackmarket/Initialize(timeofday)
for(var/market in subtypesof(/datum/blackmarket_market))
markets[market] += new market
for(var/item in subtypesof(/datum/blackmarket_item))
var/datum/blackmarket_item/I = new item()
if(!I.item)
continue
for(var/M in I.markets)
if(!markets[M])
stack_trace("SSblackmarket: Item [I] available in market that does not exist.")
continue
markets[M].add_item(item)
qdel(I)
. = ..()
/datum/controller/subsystem/blackmarket/fire(resumed)
while(length(queued_purchases))
var/datum/blackmarket_purchase/purchase = queued_purchases[1]
queued_purchases.Cut(1,2)
if(!purchase.uplink || QDELETED(purchase.uplink)) // Uh oh, uplink is gone. We will just keep the money and you will not get your order.
queued_purchases -= purchase
qdel(purchase)
continue
switch(purchase.method)
if(SHIPPING_METHOD_LTSRBT) // Find a ltsrbt pad and make it handle the shipping.
if(!telepads.len)
continue
var/free_pad_found = FALSE // Prioritize pads that don't have a cooldown active.
for(var/obj/machinery/ltsrbt/pad in telepads)
if(pad.recharge_cooldown)
continue
pad.add_to_queue(purchase)
queued_purchases -= purchase
free_pad_found = TRUE
break
if(free_pad_found)
continue
var/obj/machinery/ltsrbt/pad = pick(telepads)
to_chat(recursive_loc_check(purchase.uplink.loc, /mob), "<span class='notice'>[purchase.uplink] flashes a message noting that the order is being processed by [pad].</span>")
queued_purchases -= purchase
pad.add_to_queue(purchase)
if(SHIPPING_METHOD_TELEPORT) // Get random area, throw it somewhere there.
var/turf/targetturf = get_safe_random_station_turf()
if (!targetturf) // This shouldn't happen.
continue
to_chat(recursive_loc_check(purchase.uplink.loc, /mob), "<span class='notice'>[purchase.uplink] flashes a message noting that the order is being teleported to [get_area(targetturf)] in 60 seconds.</span>")
addtimer(CALLBACK(src, /datum/controller/subsystem/blackmarket/proc/fake_teleport, purchase.entry.spawn_item(), targetturf), 60 SECONDS) // do_teleport does not want to teleport items from nullspace, so it just forceMoves and does sparks.
queued_purchases -= purchase
qdel(purchase)
if(SHIPPING_METHOD_LAUNCH) // Get the current location of the uplink if it exists, then throws the item from space at the station from a random direction.
var/startSide = pick(GLOB.cardinals)
var/turf/T = get_turf(purchase.uplink)
var/pickedloc = spaceDebrisStartLoc(startSide, T.z)
var/atom/movable/item = purchase.entry.spawn_item(pickedloc)
item.throw_at(purchase.uplink, 3, 3, spin = FALSE)
to_chat(recursive_loc_check(purchase.uplink.loc, /mob), "<span class='notice'>[purchase.uplink] flashes a message noting the order is being launched at the station from [dir2text(startSide)].</span>")
queued_purchases -= purchase
qdel(purchase)
if(MC_TICK_CHECK)
break
/datum/controller/subsystem/blackmarket/proc/fake_teleport(atom/movable/item, turf/target) // Used to make a teleportation effect as do_teleport does not like moving items from nullspace.
item.forceMove(target)
var/datum/effect_system/spark_spread/sparks = new
sparks.set_up(5, 1, target)
sparks.attach(item)
sparks.start()
/datum/controller/subsystem/blackmarket/proc/queue_item(datum/blackmarket_purchase/P) // Used to add /datum/blackmarket_purchase to queued_purchases var. Returns TRUE when queued.
if(P.method == SHIPPING_METHOD_LTSRBT && !telepads.len)
return FALSE
queued_purchases += P
return TRUE
+176 -165
View File
@@ -3,7 +3,7 @@ SUBSYSTEM_DEF(dbcore)
flags = SS_BACKGROUND
wait = 1 MINUTES
init_order = INIT_ORDER_DBCORE
var/const/FAILED_DB_CONNECTION_CUTOFF = 5
var/failed_connection_timeout = 0
var/schema_mismatch = 0
var/db_minor = 0
@@ -13,8 +13,7 @@ SUBSYSTEM_DEF(dbcore)
var/last_error
var/list/active_queries = list()
var/datum/BSQL_Connection/connection
var/datum/BSQL_Operation/connectOperation
var/connection // Arbitrary handle returned from rust_g.
/datum/controller/subsystem/dbcore/Initialize()
//We send warnings to the admins during subsystem init, as the clients will be New'd and messages
@@ -29,7 +28,7 @@ SUBSYSTEM_DEF(dbcore)
/datum/controller/subsystem/dbcore/fire()
for(var/I in active_queries)
var/datum/DBQuery/Q = I
var/datum/db_query/Q = I
if(world.time - Q.last_activity_time > (5 MINUTES))
message_admins("Found undeleted query, please check the server logs and notify coders.")
log_sql("Undeleted query: \"[Q.sql]\" LA: [Q.last_activity] LAT: [Q.last_activity_time]")
@@ -39,24 +38,25 @@ SUBSYSTEM_DEF(dbcore)
/datum/controller/subsystem/dbcore/Recover()
connection = SSdbcore.connection
connectOperation = SSdbcore.connectOperation
/datum/controller/subsystem/dbcore/Shutdown()
//This is as close as we can get to the true round end before Disconnect() without changing where it's called, defeating the reason this is a subsystem
if(SSdbcore.Connect())
var/datum/DBQuery/query_round_shutdown = SSdbcore.NewQuery("UPDATE [format_table_name("round")] SET shutdown_datetime = Now(), end_state = '[sanitizeSQL(SSticker.end_state)]' WHERE id = [GLOB.round_id]")
var/datum/db_query/query_round_shutdown = SSdbcore.NewQuery(
"UPDATE [format_table_name("round")] SET shutdown_datetime = Now(), end_state = :end_state WHERE id = :round_id",
list("end_state" = SSticker.end_state, "round_id" = GLOB.round_id)
)
query_round_shutdown.Execute()
qdel(query_round_shutdown)
if(IsConnected())
Disconnect()
world.BSQL_Shutdown()
//nu
/datum/controller/subsystem/dbcore/can_vv_get(var_name)
return var_name != NAMEOF(src, connection) && var_name != NAMEOF(src, active_queries) && var_name != NAMEOF(src, connectOperation) && ..()
return var_name != NAMEOF(src, connection) && var_name != NAMEOF(src, active_queries) && ..()
/datum/controller/subsystem/dbcore/vv_edit_var(var_name, var_value)
if(var_name == NAMEOF(src, connection) || var_name == NAMEOF(src, connectOperation))
if(var_name == NAMEOF(src, connection))
return FALSE
return ..()
@@ -64,7 +64,11 @@ SUBSYSTEM_DEF(dbcore)
if(IsConnected())
return TRUE
if(failed_connections > FAILED_DB_CONNECTION_CUTOFF) //If it failed to establish a connection more than 5 times in a row, don't bother attempting to connect anymore.
if(failed_connection_timeout <= world.time) //it's been more than 5 seconds since we failed to connect, reset the counter
failed_connections = 0
if(failed_connections > 5) //If it failed to establish a connection more than 5 times in a row, don't bother attempting to connect for 5 seconds.
failed_connection_timeout = world.time + 50
return FALSE
if(!CONFIG_GET(flag/sql_enabled))
@@ -75,32 +79,33 @@ SUBSYSTEM_DEF(dbcore)
var/db = CONFIG_GET(string/feedback_database)
var/address = CONFIG_GET(string/address)
var/port = CONFIG_GET(number/port)
var/timeout = max(CONFIG_GET(number/async_query_timeout), CONFIG_GET(number/blocking_query_timeout))
var/thread_limit = CONFIG_GET(number/bsql_thread_limit)
connection = new /datum/BSQL_Connection(BSQL_CONNECTION_TYPE_MARIADB, CONFIG_GET(number/async_query_timeout), CONFIG_GET(number/blocking_query_timeout), CONFIG_GET(number/bsql_thread_limit))
var/error
if(QDELETED(connection))
connection = null
error = last_error
var/result = json_decode(rustg_sql_connect_pool(json_encode(list(
"host" = address,
"port" = port,
"user" = user,
"pass" = pass,
"db_name" = db,
"read_timeout" = timeout,
"write_timeout" = timeout,
"max_threads" = thread_limit,
))))
. = (result["status"] == "ok")
if (.)
connection = result["handle"]
else
SSdbcore.last_error = null
connectOperation = connection.BeginConnect(address, port, user, pass, db)
if(SSdbcore.last_error)
CRASH(SSdbcore.last_error)
UNTIL(connectOperation.IsComplete())
error = connectOperation.GetError()
. = !error
if (!.)
last_error = error
log_sql("Connect() failed | [error]")
connection = null
last_error = result["data"]
log_sql("Connect() failed | [last_error]")
++failed_connections
QDEL_NULL(connection)
QDEL_NULL(connectOperation)
/datum/controller/subsystem/dbcore/proc/CheckSchemaVersion()
if(CONFIG_GET(flag/sql_enabled))
if(Connect())
log_world("Database connection established.")
var/datum/DBQuery/query_db_version = NewQuery("SELECT major, minor FROM [format_table_name("schema_revision")] ORDER BY date DESC LIMIT 1")
var/datum/db_query/query_db_version = NewQuery("SELECT major, minor FROM [format_table_name("schema_revision")] ORDER BY date DESC LIMIT 1")
query_db_version.Execute()
if(query_db_version.NextRow())
db_major = text2num(query_db_version.item[1])
@@ -120,47 +125,46 @@ SUBSYSTEM_DEF(dbcore)
/datum/controller/subsystem/dbcore/proc/SetRoundID()
if(!Connect())
return
var/datum/DBQuery/query_round_initialize = SSdbcore.NewQuery("INSERT INTO [format_table_name("round")] (initialize_datetime, server_ip, server_port) VALUES (Now(), INET_ATON(IF('[world.internet_address]' LIKE '', '0', '[world.internet_address]')), '[world.port]')")
query_round_initialize.Execute()
var/datum/db_query/query_round_initialize = SSdbcore.NewQuery(
"INSERT INTO [format_table_name("round")] (initialize_datetime, server_ip, server_port) VALUES (Now(), INET_ATON(:internet_address), :port)",
list("internet_address" = world.internet_address || "0", "port" = "[world.port]")
)
query_round_initialize.Execute(async = FALSE)
GLOB.round_id = "[query_round_initialize.last_insert_id]"
qdel(query_round_initialize)
var/datum/DBQuery/query_round_last_id = SSdbcore.NewQuery("SELECT LAST_INSERT_ID()")
query_round_last_id.Execute()
if(query_round_last_id.NextRow())
GLOB.round_id = query_round_last_id.item[1]
qdel(query_round_last_id)
/datum/controller/subsystem/dbcore/proc/SetRoundStart()
if(!Connect())
return
var/datum/DBQuery/query_round_start = SSdbcore.NewQuery("UPDATE [format_table_name("round")] SET start_datetime = Now() WHERE id = [GLOB.round_id]")
var/datum/db_query/query_round_start = SSdbcore.NewQuery(
"UPDATE [format_table_name("round")] SET start_datetime = Now() WHERE id = :round_id",
list("round_id" = GLOB.round_id)
)
query_round_start.Execute()
qdel(query_round_start)
/datum/controller/subsystem/dbcore/proc/SetRoundEnd()
if(!Connect())
return
var/sql_station_name = sanitizeSQL(station_name())
var/datum/DBQuery/query_round_end = SSdbcore.NewQuery("UPDATE [format_table_name("round")] SET end_datetime = Now(), game_mode_result = '[sanitizeSQL(SSticker.mode_result)]', station_name = '[sql_station_name]' WHERE id = [GLOB.round_id]")
var/datum/db_query/query_round_end = SSdbcore.NewQuery(
"UPDATE [format_table_name("round")] SET end_datetime = Now(), game_mode_result = :game_mode_result, station_name = :station_name WHERE id = :round_id",
list("game_mode_result" = SSticker.mode_result, "station_name" = station_name(), "round_id" = GLOB.round_id)
)
query_round_end.Execute()
qdel(query_round_end)
/datum/controller/subsystem/dbcore/proc/Disconnect()
failed_connections = 0
QDEL_NULL(connectOperation)
QDEL_NULL(connection)
if (connection)
rustg_sql_disconnect_pool(connection)
connection = null
/datum/controller/subsystem/dbcore/proc/IsConnected()
if(!CONFIG_GET(flag/sql_enabled))
if (!CONFIG_GET(flag/sql_enabled))
return FALSE
//block until any connect operations finish
var/datum/BSQL_Connection/_connection = connection
var/datum/BSQL_Operation/op = connectOperation
UNTIL(QDELETED(_connection) || op.IsComplete())
return !QDELETED(connection) && !op.GetError()
/datum/controller/subsystem/dbcore/proc/Quote(str)
if(connection)
return connection.Quote(str)
if (!connection)
return FALSE
return json_decode(rustg_sql_connected(connection))["status"] == "online"
/datum/controller/subsystem/dbcore/proc/ErrorMsg()
if(!CONFIG_GET(flag/sql_enabled))
@@ -170,32 +174,34 @@ SUBSYSTEM_DEF(dbcore)
/datum/controller/subsystem/dbcore/proc/ReportError(error)
last_error = error
/datum/controller/subsystem/dbcore/proc/NewQuery(sql_query)
/datum/controller/subsystem/dbcore/proc/NewQuery(sql_query, arguments)
if(IsAdminAdvancedProcCall())
log_admin_private("ERROR: Advanced admin proc call led to sql query: [sql_query]. Query has been blocked")
message_admins("ERROR: Advanced admin proc call led to sql query. Query has been blocked")
return FALSE
return new /datum/DBQuery(sql_query, connection)
return new /datum/db_query(connection, sql_query, arguments)
/datum/controller/subsystem/dbcore/proc/QuerySelect(list/querys, warn = FALSE, qdel = FALSE)
if (!islist(querys))
if (!istype(querys, /datum/DBQuery))
if (!istype(querys, /datum/db_query))
CRASH("Invalid query passed to QuerySelect: [querys]")
querys = list(querys)
for (var/thing in querys)
var/datum/DBQuery/query = thing
var/datum/db_query/query = thing
if (warn)
INVOKE_ASYNC(query, /datum/DBQuery.proc/warn_execute)
INVOKE_ASYNC(query, /datum/db_query.proc/warn_execute)
else
INVOKE_ASYNC(query, /datum/DBQuery.proc/Execute)
INVOKE_ASYNC(query, /datum/db_query.proc/Execute)
for (var/thing in querys)
var/datum/DBQuery/query = thing
var/datum/db_query/query = thing
UNTIL(!query.in_progress)
if (qdel)
qdel(query)
/*
Takes a list of rows (each row being an associated list of column => value) and inserts them via a single mass query.
Rows missing columns present in other rows will resolve to SQL NULL
@@ -203,137 +209,135 @@ You are expected to do your own escaping of the data, and expected to provide yo
The duplicate_key arg can be true to automatically generate this part of the query
or set to a string that is appended to the end of the query
Ignore_errors instructes mysql to continue inserting rows if some of them have errors.
the erroneous row(s) aren't inserted and there isn't really any way to know why or why errored
the erroneous row(s) aren't inserted and there isn't really any way to know why or why errored
Delayed insert mode was removed in mysql 7 and only works with MyISAM type tables,
It was included because it is still supported in mariadb.
It does not work with duplicate_key and the mysql server ignores it in those cases
*/
/datum/controller/subsystem/dbcore/proc/MassInsert(table, list/rows, duplicate_key = FALSE, ignore_errors = FALSE, delayed = FALSE, warn = FALSE, async = TRUE)
/datum/controller/subsystem/dbcore/proc/MassInsert(table, list/rows, duplicate_key = FALSE, ignore_errors = FALSE, delayed = FALSE, warn = FALSE, async = TRUE, special_columns = null)
if (!table || !rows || !istype(rows))
return
// Prepare column list
var/list/columns = list()
var/list/sorted_rows = list()
var/list/has_question_mark = list()
for (var/list/row in rows)
var/list/sorted_row = list()
sorted_row.len = columns.len
for (var/column in row)
var/idx = columns[column]
if (!idx)
idx = columns.len + 1
columns[column] = idx
sorted_row.len = columns.len
columns[column] = "?"
has_question_mark[column] = TRUE
for (var/column in special_columns)
columns[column] = special_columns[column]
has_question_mark[column] = findtext(special_columns[column], "?")
sorted_row[idx] = row[column]
sorted_rows[++sorted_rows.len] = sorted_row
// Prepare SQL query full of placeholders
var/list/query_parts = list("INSERT")
if (delayed)
query_parts += " DELAYED"
if (ignore_errors)
query_parts += " IGNORE"
query_parts += " INTO "
query_parts += table
query_parts += "\n([columns.Join(", ")])\nVALUES"
var/list/arguments = list()
var/has_row = FALSE
for (var/list/row in rows)
if (has_row)
query_parts += ","
query_parts += "\n ("
var/has_col = FALSE
for (var/column in columns)
if (has_col)
query_parts += ", "
if (has_question_mark[column])
var/name = "p[arguments.len]"
query_parts += replacetext(columns[column], "?", ":[name]")
arguments[name] = row[column]
else
query_parts += columns[column]
has_col = TRUE
query_parts += ")"
has_row = TRUE
if (duplicate_key == TRUE)
var/list/column_list = list()
for (var/column in columns)
column_list += "[column] = VALUES([column])"
duplicate_key = "ON DUPLICATE KEY UPDATE [column_list.Join(", ")]\n"
else if (duplicate_key == FALSE)
duplicate_key = null
query_parts += "\nON DUPLICATE KEY UPDATE [column_list.Join(", ")]"
else if (duplicate_key != FALSE)
query_parts += duplicate_key
if (ignore_errors)
ignore_errors = " IGNORE"
else
ignore_errors = null
if (delayed)
delayed = " DELAYED"
else
delayed = null
var/list/sqlrowlist = list()
var/len = columns.len
for (var/list/row in sorted_rows)
if (length(row) != len)
row.len = len
for (var/value in row)
if (value == null)
value = "NULL"
sqlrowlist += "([row.Join(", ")])"
sqlrowlist = " [sqlrowlist.Join(",\n ")]"
var/datum/DBQuery/Query = NewQuery("INSERT[delayed][ignore_errors] INTO [table]\n([columns.Join(", ")])\nVALUES\n[sqlrowlist]\n[duplicate_key]")
var/datum/db_query/Query = NewQuery(query_parts.Join(), arguments)
if (warn)
. = Query.warn_execute(async)
else
. = Query.Execute(async)
qdel(Query)
/datum/DBQuery
var/sql // The sql query being executed.
var/list/item //list of data values populated by NextRow()
/datum/db_query
// Inputs
var/connection
var/sql
var/arguments
// Status information
var/in_progress
var/last_error
var/last_activity
var/last_activity_time
var/last_error
var/skip_next_is_complete
var/in_progress
var/datum/BSQL_Connection/connection
var/datum/BSQL_Operation/Query/query
// Output
var/list/list/rows
var/next_row_to_take = 1
var/affected
var/last_insert_id
/datum/DBQuery/New(sql_query, datum/BSQL_Connection/connection)
var/list/item //list of data values populated by NextRow()
/datum/db_query/New(connection, sql, arguments)
SSdbcore.active_queries[src] = TRUE
Activity("Created")
item = list()
src.connection = connection
sql = sql_query
/datum/DBQuery/Destroy()
src.connection = connection
src.sql = sql
src.arguments = arguments
/datum/db_query/Destroy()
Close()
SSdbcore.active_queries -= src
return ..()
/datum/DBQuery/CanProcCall(proc_name)
/datum/db_query/CanProcCall(proc_name)
//fuck off kevinz
return FALSE
/datum/DBQuery/proc/SetQuery(new_sql)
if(in_progress)
CRASH("Attempted to set new sql while waiting on active query")
Close()
sql = new_sql
/datum/DBQuery/proc/Activity(activity)
/datum/db_query/proc/Activity(activity)
last_activity = activity
last_activity_time = world.time
/datum/DBQuery/proc/warn_execute(async = FALSE)
/datum/db_query/proc/warn_execute(async = TRUE)
. = Execute(async)
if(!.)
to_chat(usr, "<span class='danger'>A SQL error occurred during this operation, check the server logs.</span>")
/datum/DBQuery/proc/Execute(async = FALSE, log_error = TRUE)
/datum/db_query/proc/Execute(async = TRUE, log_error = TRUE)
Activity("Execute")
if(in_progress)
CRASH("Attempted to start a new query while waiting on the old one")
if(QDELETED(connection))
if(!SSdbcore.IsConnected())
last_error = "No connection!"
return FALSE
var/start_time
var/timed_out
if(!async)
start_time = REALTIMEOFDAY
Close()
query = connection.BeginQuery(sql)
if(!async)
timed_out = !query.WaitForCompletion()
else
in_progress = TRUE
UNTIL(query.IsComplete())
in_progress = FALSE
skip_next_is_complete = TRUE
var/error = QDELETED(query) ? "Query object deleted!" : query.GetError()
last_error = error
. = !error
. = run_query(async)
var/timed_out = !. && findtext(last_error, "Operation timed out")
if(!. && log_error)
log_sql("[error] | Query used: [sql]")
log_sql("[last_error] | Query used: [sql] | Arguments: [json_encode(arguments)]")
if(!async && timed_out)
log_query_debug("Query execution started at [start_time]")
log_query_debug("Query execution ended at [REALTIMEOFDAY]")
@@ -341,44 +345,51 @@ Delayed insert mode was removed in mysql 7 and only works with MyISAM type table
log_query_debug("Query used: [sql]")
slow_query_check()
/datum/DBQuery/proc/slow_query_check()
/datum/db_query/proc/run_query(async)
var/job_result_str
if (async)
var/job_id = rustg_sql_query_async(connection, sql, json_encode(arguments))
in_progress = TRUE
UNTIL((job_result_str = rustg_sql_check_query(job_id)) != RUSTG_JOB_NO_RESULTS_YET)
in_progress = FALSE
if (job_result_str == RUSTG_JOB_ERROR)
last_error = job_result_str
return FALSE
else
job_result_str = rustg_sql_query_blocking(connection, sql, json_encode(arguments))
var/result = json_decode(job_result_str)
switch (result["status"])
if ("ok")
rows = result["rows"]
affected = result["affected"]
last_insert_id = result["last_insert_id"]
return TRUE
if ("err")
last_error = result["data"]
return FALSE
if ("offline")
last_error = "offline"
return FALSE
/datum/db_query/proc/slow_query_check()
message_admins("HEY! A database query timed out. Did the server just hang? <a href='?_src_=holder;[HrefToken()];slowquery=yes'>\[YES\]</a>|<a href='?_src_=holder;[HrefToken()];slowquery=no'>\[NO\]</a>")
/datum/DBQuery/proc/NextRow(async)
/datum/db_query/proc/NextRow(async = TRUE)
Activity("NextRow")
UNTIL(!in_progress)
if(!skip_next_is_complete)
if(!async)
query.WaitForCompletion()
else
in_progress = TRUE
UNTIL(query.IsComplete())
in_progress = FALSE
if (rows && next_row_to_take <= rows.len)
item = rows[next_row_to_take]
next_row_to_take++
return !!item
else
skip_next_is_complete = FALSE
return FALSE
last_error = query.GetError()
var/list/results = query.CurrentRow()
. = results != null
item.Cut()
//populate item array
for(var/I in results)
item += results[I]
/datum/DBQuery/proc/ErrorMsg()
/datum/db_query/proc/ErrorMsg()
return last_error
/datum/DBQuery/proc/Close()
item.Cut()
QDEL_NULL(query)
/world/BSQL_Debug(message)
if(!CONFIG_GET(flag/bsql_debug))
return
//strip sensitive stuff
if(findtext(message, ": CreateConnection("))
message = "CreateConnection CENSORED"
log_sql("BSQL_DEBUG: [message]")
/datum/db_query/proc/Close()
rows = null
item = null
+24
View File
@@ -490,6 +490,30 @@ SUBSYSTEM_DEF(job)
job.after_spawn(H, M, joined_late) // note: this happens before the mob has a key! M will always have a client, H might not.
equip_loadout(N, H, TRUE)//CIT CHANGE - makes players spawn with in-backpack loadout items properly. A little hacky but it works
var/list/tcg_cards
if(ishuman(H))
if(length(H.client?.prefs?.tcg_cards))
tcg_cards = H.client.prefs.tcg_cards
else if(length(N?.client?.prefs?.tcg_cards))
tcg_cards = N.client.prefs.tcg_cards
if(tcg_cards)
var/obj/item/tcgcard_binder/binder = new(get_turf(H))
H.equip_to_slot_if_possible(binder, SLOT_IN_BACKPACK, disable_warning = TRUE, bypass_equip_delay_self = TRUE)
for(var/card_type in N.client.prefs.tcg_cards)
if(card_type)
if(islist(H.client.prefs.tcg_cards[card_type]))
for(var/duplicate in N.client.prefs.tcg_cards[card_type])
var/obj/item/tcg_card/card = new(get_turf(H), card_type, duplicate)
card.forceMove(binder)
binder.cards.Add(card)
else
var/obj/item/tcg_card/card = new(get_turf(H), card_type, N.client.prefs.tcg_cards[card_type])
card.forceMove(binder)
binder.cards.Add(card)
binder.check_for_exodia()
if(length(N.client.prefs.tcg_decks))
binder.decks = N.client.prefs.tcg_decks
return H
/*
/datum/controller/subsystem/job/proc/handle_auto_deadmin_roles(client/C, rank)
+29 -6
View File
@@ -34,6 +34,9 @@ SUBSYSTEM_DEF(mapping)
var/list/reservation_ready = list()
var/clearing_reserved_turfs = FALSE
///All possible biomes in assoc list as type || instance
var/list/biomes = list()
// Z-manager stuff
var/station_start // should only be used for maploading-related tasks
var/space_levels_so_far = 0
@@ -79,10 +82,12 @@ SUBSYSTEM_DEF(mapping)
config = old_config
GLOB.year_integer += config.year_offset
GLOB.announcertype = (config.announcertype == "standard" ? (prob(1) ? "medibot" : "classic") : config.announcertype)
initialize_biomes()
loadWorld()
repopulate_sorted_areas()
process_teleport_locs() //Sets up the wizard teleport locations
preloadTemplates()
#ifndef LOWMEMORYMODE
// Create space ruin levels
while (space_levels_so_far < config.space_ruin_levels)
@@ -92,15 +97,16 @@ SUBSYSTEM_DEF(mapping)
for (var/i in 1 to config.space_empty_levels)
++space_levels_so_far
empty_space = add_new_zlevel("Empty Area [space_levels_so_far]", list(ZTRAIT_LINKAGE = CROSSLINKED))
// and the transit level
transit = add_new_zlevel("Transit/Reserved", list(ZTRAIT_RESERVED = TRUE))
// Pick a random away mission.
if(CONFIG_GET(flag/roundstart_away))
createRandomZlevel()
// Pick a random VR level.
// Load the virtual reality hub
if(CONFIG_GET(flag/roundstart_vr))
to_chat(world, "<span class='boldannounce'>Loading virtual reality...</span>")
createRandomZlevel(VIRT_REALITY_NAME, list(ZTRAIT_AWAY = TRUE, ZTRAIT_VR = TRUE), GLOB.potential_vr_levels)
to_chat(world, "<span class='boldannounce'>Virtual reality loaded.</span>")
// Generate mining ruins
loading_ruins = TRUE
@@ -115,7 +121,7 @@ SUBSYSTEM_DEF(mapping)
// needs to be whitelisted for underground too so place_below ruins work
seedRuins(ice_ruins, CONFIG_GET(number/icemoon_budget), list(/area/icemoon/surface/outdoors/unexplored, /area/icemoon/underground/unexplored), ice_ruins_templates)
for (var/ice_z in ice_ruins)
spawn_rivers(ice_z, 4, /turf/open/transparent/openspace/icemoon, /area/icemoon/surface/outdoors/unexplored/rivers)
spawn_rivers(ice_z, 4, /turf/open/openspace/icemoon, /area/icemoon/surface/outdoors/unexplored/rivers)
var/list/ice_ruins_underground = levels_by_trait(ZTRAIT_ICE_RUINS_UNDERGROUND)
if (ice_ruins_underground.len)
@@ -133,8 +139,13 @@ SUBSYSTEM_DEF(mapping)
if (station_ruins.len)
seedRuins(station_ruins, (SSmapping.config.station_ruin_budget < 0) ? CONFIG_GET(number/station_space_budget) : SSmapping.config.station_ruin_budget, list(/area/space/station_ruins), station_ruins_templates)
SSmapping.seedStation()
loading_ruins = FALSE
#endif
// Run map generation after ruin generation to prevent issues
run_map_generation()
// Add the transit level
transit = add_new_zlevel("Transit/Reserved", list(ZTRAIT_RESERVED = TRUE))
repopulate_sorted_areas()
// Set up Z-level transitions.
setup_map_transitions()
@@ -286,7 +297,9 @@ SUBSYSTEM_DEF(mapping)
setup_station_z_index()
if(SSdbcore.Connect())
var/datum/DBQuery/query_round_map_name = SSdbcore.NewQuery("UPDATE [format_table_name("round")] SET map_name = '[config.map_name]' WHERE id = [GLOB.round_id]")
var/datum/db_query/query_round_map_name = SSdbcore.NewQuery({"
UPDATE [format_table_name("round")] SET map_name = :map_name WHERE id = :round_id
"}, list("map_name" = config.map_name, "round_id" = GLOB.round_id))
query_round_map_name.Execute()
qdel(query_round_map_name)
@@ -318,7 +331,7 @@ GLOBAL_LIST_EMPTY(the_station_areas)
for(var/area/A in world)
if (is_type_in_typecache(A, station_areas_blacklist))
continue
if (!A.contents.len || !A.unique)
if (!A.contents.len || !(A.area_flags & UNIQUE_AREA))
continue
var/turf/picked = A.contents[1]
if (is_station_level(picked.z))
@@ -327,6 +340,10 @@ GLOBAL_LIST_EMPTY(the_station_areas)
if(!GLOB.the_station_areas.len)
log_world("ERROR: Station areas list failed to generate!")
/datum/controller/subsystem/mapping/proc/run_map_generation()
for(var/area/A in world)
A.RunGeneration()
/datum/controller/subsystem/mapping/proc/maprotate()
var/players = GLOB.clients.len
var/list/mapvotes = list()
@@ -582,6 +599,12 @@ GLOBAL_LIST_EMPTY(the_station_areas)
used_turfs.Cut()
reserve_turfs(clearing)
///Initialize all biomes, assoc as type || instance
/datum/controller/subsystem/mapping/proc/initialize_biomes()
for(var/biome_path in subtypesof(/datum/biome))
var/datum/biome/biome_instance = new biome_path()
biomes[biome_path] += biome_instance
/datum/controller/subsystem/mapping/proc/reg_in_areas_in_z(list/areas)
for(var/B in areas)
var/area/A = B
+16 -7
View File
@@ -1,6 +1,8 @@
/*! How material datums work
Materials are now instanced datums, with an associative list of them being kept in SSmaterials. We only instance the materials once and then re-use these instances for everything.
These materials call on_applied() on whatever item they are applied to, common effects are adding components, changing color and changing description. This allows us to differentiate items based on the material they are made out of.area
*/
SUBSYSTEM_DEF(materials)
@@ -14,12 +16,16 @@ SUBSYSTEM_DEF(materials)
var/list/materialtypes_by_category
///A cache of all material combinations that have been used
var/list/list/material_combos
///List of stackcrafting recipes for materials using rigid materials
///List of stackcrafting recipes for materials using base recipes
var/list/base_stack_recipes = list(
new /datum/stack_recipe("Chair", /obj/structure/chair/greyscale, one_per_turf = TRUE, on_floor = TRUE, applies_mats = TRUE),
new /datum/stack_recipe("Toilet", /obj/structure/toilet/greyscale, one_per_turf = TRUE, on_floor = TRUE, applies_mats = TRUE),
new /datum/stack_recipe("Sink Frame", /obj/structure/sink/greyscale, one_per_turf = TRUE, on_floor = TRUE, applies_mats = TRUE),
new /datum/stack_recipe("Floor tile", /obj/item/stack/tile/material, 1, 4, 20, applies_mats = TRUE),
)
///List of stackcrafting recipes for materials using rigid recipes
var/list/rigid_stack_recipes = list(
new /datum/stack_recipe("chair", /obj/structure/chair/greyscale, one_per_turf = TRUE, on_floor = TRUE, applies_mats = TRUE),
new /datum/stack_recipe("toilet", /obj/structure/toilet/greyscale, one_per_turf = TRUE, on_floor = TRUE, applies_mats = TRUE),
new /datum/stack_recipe("sink", /obj/structure/sink/greyscale, one_per_turf = TRUE, on_floor = TRUE, applies_mats = TRUE),
new /datum/stack_recipe("Floor tile", /obj/item/stack/tile/material, 1, 4, 20, applies_mats = TRUE)
// new /datum/stack_recipe("Carving block", /obj/structure/carving_block, 5, one_per_turf = TRUE, on_floor = TRUE, applies_mats = TRUE),
)
///Ran on initialize, populated the materials and materials_by_category dictionaries with their appropiate vars (See these variables for more info)
@@ -29,7 +35,11 @@ SUBSYSTEM_DEF(materials)
materialtypes_by_category = list()
material_combos = list()
for(var/type in subtypesof(/datum/material))
var/datum/material/ref = new type
var/datum/material/ref = type
// if(!(initial(ref.init_flags) & MATERIAL_INIT_MAPLOAD))
// continue // Do not initialize
ref = new ref
materials[type] = ref
for(var/c in ref.categories)
materials_by_category[c] += list(ref)
@@ -40,7 +50,6 @@ SUBSYSTEM_DEF(materials)
InitializeMaterials()
return materials[fakemat] || fakemat
///Returns a list to be used as an object's custom_materials. Lists will be cached and re-used based on the parameters.
/datum/controller/subsystem/materials/proc/FindOrCreateMaterialCombo(list/materials_declaration, multiplier)
if(!material_combos)
-87
View File
@@ -1,87 +0,0 @@
SUBSYSTEM_DEF(medals)
name = "Medals"
flags = SS_NO_FIRE
var/hub_enabled = FALSE
/datum/controller/subsystem/medals/Initialize(timeofday)
if(CONFIG_GET(string/medal_hub_address) && CONFIG_GET(string/medal_hub_password))
hub_enabled = TRUE
return ..()
/datum/controller/subsystem/medals/proc/UnlockMedal(medal, client/player)
set waitfor = FALSE
if(!medal || !hub_enabled)
return
if(isnull(world.SetMedal(medal, player, CONFIG_GET(string/medal_hub_address), CONFIG_GET(string/medal_hub_password))))
hub_enabled = FALSE
log_game("MEDAL ERROR: Could not contact hub to award medal:[medal] player:[player.key]")
message_admins("Error! Failed to contact hub to award [medal] medal to [player.key]!")
return
to_chat(player, "<span class='greenannounce'><B>Achievement unlocked: [medal]!</B></span>")
/datum/controller/subsystem/medals/proc/SetScore(score, client/player, increment, force)
set waitfor = FALSE
if(!score || !hub_enabled)
return
var/list/oldscore = GetScore(score, player, TRUE)
if(increment)
if(!oldscore[score])
oldscore[score] = 1
else
oldscore[score] = (text2num(oldscore[score]) + 1)
else
oldscore[score] = force
var/newscoreparam = list2params(oldscore)
if(isnull(world.SetScores(player.ckey, newscoreparam, CONFIG_GET(string/medal_hub_address), CONFIG_GET(string/medal_hub_password))))
hub_enabled = FALSE
log_game("SCORE ERROR: Could not contact hub to set score. Score:[score] player:[player.key]")
message_admins("Error! Failed to contact hub to set [score] score for [player.key]!")
/datum/controller/subsystem/medals/proc/GetScore(score, client/player, returnlist)
if(!score || !hub_enabled)
return
var/scoreget = world.GetScores(player.ckey, score, CONFIG_GET(string/medal_hub_address), CONFIG_GET(string/medal_hub_password))
if(isnull(scoreget))
hub_enabled = FALSE
log_game("SCORE ERROR: Could not contact hub to get score. Score:[score] player:[player.key]")
message_admins("Error! Failed to contact hub to get score: [score] for [player.key]!")
return
. = params2list(scoreget)
if(!returnlist)
return .[score]
/datum/controller/subsystem/medals/proc/CheckMedal(medal, client/player)
if(!medal || !hub_enabled)
return
if(isnull(world.GetMedal(medal, player, CONFIG_GET(string/medal_hub_address), CONFIG_GET(string/medal_hub_password))))
hub_enabled = FALSE
log_game("MEDAL ERROR: Could not contact hub to get medal:[medal] player: [player.key]")
message_admins("Error! Failed to contact hub to get [medal] medal for [player.key]!")
return
to_chat(player, "[medal] is unlocked")
/datum/controller/subsystem/medals/proc/LockMedal(medal, client/player)
if(!player || !medal || !hub_enabled)
return
var/result = world.ClearMedal(medal, player, CONFIG_GET(string/medal_hub_address), CONFIG_GET(string/medal_hub_password))
switch(result)
if(null)
hub_enabled = FALSE
log_game("MEDAL ERROR: Could not contact hub to clear medal:[medal] player:[player.key]")
message_admins("Error! Failed to contact hub to clear [medal] medal for [player.key]!")
if(TRUE)
message_admins("Medal: [medal] removed for [player.key]")
if(FALSE)
message_admins("Medal: [medal] was not found for [player.key]. Unable to clear.")
/datum/controller/subsystem/medals/proc/ClearScore(client/player)
if(isnull(world.SetScores(player.ckey, "", CONFIG_GET(string/medal_hub_address), CONFIG_GET(string/medal_hub_password))))
log_game("MEDAL ERROR: Could not contact hub to clear scores for [player.key]!")
message_admins("Error! Failed to contact hub to clear scores for [player.key]!")
+1 -1
View File
@@ -1,7 +1,7 @@
SUBSYSTEM_DEF(min_spawns)
name = "Minimum Spawns" /// this hot steaming pile of garbage makes sure theres a minimum of tendrils scattered around
init_order = INIT_ORDER_DEFAULT
flags = SS_BACKGROUND | SS_NO_FIRE
flags = SS_NO_FIRE | SS_NO_INIT
wait = 2
var/where_we_droppin_boys_iterations = 0
var/snaxi_snowflake_check = FALSE
@@ -349,3 +349,15 @@ SUBSYSTEM_DEF(persistence)
if(!ending_human.client)
return
ending_human.client.prefs.save_character()
/datum/controller/subsystem/persistence/proc/SaveTCGCards()
for(var/i in GLOB.joined_player_list)
var/mob/living/carbon/human/ending_human = get_mob_by_ckey(i)
if(!istype(ending_human) || !ending_human.mind || !ending_human.client || !ending_human.client.prefs || !ending_human.client.prefs.tcg_cards)
continue
var/mob/living/carbon/human/original_human = ending_human.mind.original_character
if(!original_human || original_human.stat == DEAD || !(original_human == ending_human))
continue
ending_human.SaveTCGCards()
@@ -3,9 +3,10 @@
*/
/datum/controller/subsystem/persistence
var/list/saved_modes = list(1,2,3)
var/list/saved_chaos = list(5,5,5)
var/list/saved_dynamic_rules = list(list(),list(),list())
var/list/saved_storytellers = list("foo","bar","baz")
var/list/average_dynamic_threat = 50
var/average_threat = 50
var/list/saved_maps
/datum/controller/subsystem/persistence/SaveServerPersistence()
@@ -20,6 +21,7 @@
/datum/controller/subsystem/persistence/LoadServerPersistence()
. = ..()
LoadRecentModes()
LoadRecentChaos()
LoadRecentStorytellers()
LoadRecentRulesets()
LoadRecentMaps()
@@ -33,16 +35,24 @@
file_data["data"] = saved_modes
fdel(json_file)
WRITE_FILE(json_file, json_encode(file_data))
saved_chaos[3] = saved_chaos[2]
saved_chaos[2] = saved_chaos[1]
saved_chaos[1] = SSticker.mode.get_chaos()
average_threat = (SSactivity.get_average_threat() + average_threat) / 2
json_file = file("data/RecentChaos.json")
file_data = list()
file_data["data"] = saved_chaos + average_threat
fdel(json_file)
WRITE_FILE(json_file, json_encode(file_data))
/datum/controller/subsystem/persistence/proc/CollectStoryteller(var/datum/game_mode/dynamic/mode)
saved_storytellers.len = 3
saved_storytellers[3] = saved_storytellers[2]
saved_storytellers[2] = saved_storytellers[1]
saved_storytellers[1] = mode.storyteller.name
average_dynamic_threat = (mode.max_threat + average_dynamic_threat) / 2
var/json_file = file("data/RecentStorytellers.json")
var/list/file_data = list()
file_data["data"] = saved_storytellers + average_dynamic_threat
file_data["data"] = saved_storytellers
fdel(json_file)
WRITE_FILE(json_file, json_encode(file_data))
@@ -76,6 +86,18 @@
return
saved_modes = json["data"]
/datum/controller/subsystem/persistence/proc/LoadRecentChaos()
var/json_file = file("data/RecentChaos.json")
if(!fexists(json_file))
return
var/list/json = json_decode(file2text(json_file))
if(!json)
return
saved_chaos = json["data"]
if(saved_chaos.len > 3)
average_threat = saved_chaos[4]
saved_chaos.len = 3
/datum/controller/subsystem/persistence/proc/LoadRecentRulesets()
var/json_file = file("data/RecentRulesets.json")
if(!fexists(json_file))
@@ -93,9 +115,6 @@
if(!json)
return
saved_storytellers = json["data"]
if(saved_storytellers.len > 3)
average_dynamic_threat = saved_storytellers[4]
saved_storytellers.len = 3
/datum/controller/subsystem/persistence/proc/LoadRecentMaps()
var/json_file = file("data/RecentMaps.json")
@@ -105,3 +124,9 @@
if(!json)
return
saved_maps = json["maps"]
/datum/controller/subsystem/persistence/proc/get_recent_chaos()
var/sum = 0
for(var/n in saved_chaos)
sum += n
return sum/length(saved_chaos)
@@ -47,5 +47,5 @@ SUBSYSTEM_DEF(processing)
* If you override this do not call parent, as it will return PROCESS_KILL. This is done to prevent objects that dont override process() from staying in the processing list
*/
/datum/proc/process(delta_time)
set waitfor = FALSE
// SHOULD_NOT_SLEEP(TRUE)
return PROCESS_KILL
@@ -70,3 +70,8 @@ PROCESSING_SUBSYSTEM_DEF(weather)
A = W
break
return A
/datum/controller/subsystem/processing/weather/proc/get_weather_by_type(datum/weather/weather_datum_type)
for(var/V in processing)
if(istype(V,weather_datum_type))
return V
+72 -42
View File
@@ -12,6 +12,10 @@ SUBSYSTEM_DEF(shuttle)
var/list/beacons = list()
var/list/transit = list()
//Now it only for ID generation
var/list/assoc_mobile = list()
var/list/assoc_stationary = list()
var/list/transit_requesters = list()
var/list/transit_request_failures = list()
@@ -26,6 +30,7 @@ SUBSYSTEM_DEF(shuttle)
var/emergencyCallAmount = 0 //how many times the escape shuttle was called
var/emergencyNoEscape
var/emergencyNoRecall = FALSE
var/adminEmergencyNoRecall = FALSE
var/list/hostileEnvironments = list() //Things blocking escape shuttle from leaving
var/list/tradeBlockade = list() //Things blocking cargo from leaving.
var/supplyBlocked = FALSE
@@ -65,6 +70,8 @@ SUBSYSTEM_DEF(shuttle)
var/datum/turf_reservation/preview_reservation
var/shuttle_loading
/datum/controller/subsystem/shuttle/Initialize(timeofday)
ordernum = rand(1, 9000)
@@ -134,7 +141,7 @@ SUBSYSTEM_DEF(shuttle)
break
/datum/controller/subsystem/shuttle/proc/CheckAutoEvac()
if(emergencyNoEscape || emergencyNoRecall || !emergency || !SSticker.HasRoundStarted())
if(emergencyNoEscape || adminEmergencyNoRecall || emergencyNoRecall || !emergency || !SSticker.HasRoundStarted())
return
var/threshold = CONFIG_GET(number/emergency_shuttle_autocall_threshold)
@@ -179,31 +186,26 @@ SUBSYSTEM_DEF(shuttle)
return S
WARNING("couldn't find dock with id: [id]")
/// Check if we can call the evac shuttle.
/// Returns TRUE if we can. Otherwise, returns a string detailing the problem.
/datum/controller/subsystem/shuttle/proc/canEvac(mob/user)
var/srd = CONFIG_GET(number/shuttle_refuel_delay)
if(world.time - SSticker.round_start_time < srd)
to_chat(user, "<span class='alert'>The emergency shuttle is refueling. Please wait [DisplayTimeText(srd - (world.time - SSticker.round_start_time))] before trying again.</span>")
return FALSE
return "The emergency shuttle is refueling. Please wait [DisplayTimeText(srd - (world.time - SSticker.round_start_time))] before attempting to call."
switch(emergency.mode)
if(SHUTTLE_RECALL)
to_chat(user, "<span class='alert'>The emergency shuttle may not be called while returning to CentCom.</span>")
return FALSE
return "The emergency shuttle may not be called while returning to CentCom."
if(SHUTTLE_CALL)
to_chat(user, "<span class='alert'>The emergency shuttle is already on its way.</span>")
return FALSE
return "The emergency shuttle is already on its way."
if(SHUTTLE_DOCKED)
to_chat(user, "<span class='alert'>The emergency shuttle is already here.</span>")
return FALSE
return "The emergency shuttle is already here."
if(SHUTTLE_IGNITING)
to_chat(user, "<span class='alert'>The emergency shuttle is firing its engines to leave.</span>")
return FALSE
return "The emergency shuttle is firing its engines to leave."
if(SHUTTLE_ESCAPE)
to_chat(user, "<span class='alert'>The emergency shuttle is moving away to a safe distance.</span>")
return FALSE
return "The emergency shuttle is moving away to a safe distance."
if(SHUTTLE_STRANDED)
to_chat(user, "<span class='alert'>The emergency shuttle has been disabled by CentCom.</span>")
return FALSE
return "The emergency shuttle has been disabled by CentCom."
return TRUE
@@ -221,7 +223,9 @@ SUBSYSTEM_DEF(shuttle)
Good luck.")
emergency = backup_shuttle
if(!canEvac(user))
var/can_evac_or_fail_reason = SSshuttle.canEvac(user)
if(can_evac_or_fail_reason != TRUE)
to_chat(user, "<span class='alert'>[can_evac_or_fail_reason]</span>")
return
call_reason = trim(html_encode(call_reason))
@@ -250,10 +254,11 @@ SUBSYSTEM_DEF(shuttle)
var/area/A = get_area(user)
log_shuttle("[key_name(user)] has called the emergency shuttle.")
deadchat_broadcast(" has called the shuttle at <span class='name'>[A.name]</span>.", "<span class='name'>[user.real_name]</span>", user)
deadchat_broadcast(" has called the shuttle at <span class='name'>[A.name]</span>.", "<span class='name'>[user.real_name]</span>", user) //, message_type=DEADCHAT_ANNOUNCEMENT)
if(call_reason)
SSblackbox.record_feedback("text", "shuttle_reason", 1, "[call_reason]")
log_shuttle("Shuttle call reason: [call_reason]")
SSticker.emergency_reason = call_reason
message_admins("[ADMIN_LOOKUPFLW(user)] has called the shuttle. (<A HREF='?_src_=holder;[HrefToken()];trigger_centcom_recall=1'>TRIGGER CENTCOM RECALL</A>)")
/datum/controller/subsystem/shuttle/proc/centcom_recall(old_timer, admiral_message)
@@ -288,7 +293,7 @@ SUBSYSTEM_DEF(shuttle)
emergency.cancel(get_area(user))
log_shuttle("[key_name(user)] has recalled the shuttle.")
message_admins("[ADMIN_LOOKUPFLW(user)] has recalled the shuttle.")
deadchat_broadcast(" has recalled the shuttle from <span class='name'>[get_area_name(user, TRUE)]</span>.", "<span class='name'>[user.real_name]</span>", user)
deadchat_broadcast(" has recalled the shuttle from <span class='name'>[get_area_name(user, TRUE)]</span>.", "<span class='name'>[user.real_name]</span>", user) //, message_type=DEADCHAT_ANNOUNCEMENT)
return 1
/datum/controller/subsystem/shuttle/proc/canRecall()
@@ -314,7 +319,7 @@ SUBSYSTEM_DEF(shuttle)
if (!SSticker.IsRoundInProgress())
return
var/callShuttle = 1
var/callShuttle = TRUE
for(var/thing in GLOB.shuttle_caller_list)
if(isAI(thing))
@@ -330,7 +335,7 @@ SUBSYSTEM_DEF(shuttle)
var/turf/T = get_turf(thing)
if(T && is_station_level(T.z))
callShuttle = 0
callShuttle = FALSE
break
if(callShuttle)
@@ -406,7 +411,7 @@ SUBSYSTEM_DEF(shuttle)
else
if(M.initiate_docking(getDock(destination)) != DOCKING_SUCCESS)
return 2
return 0 //dock successful
return 0 //dock successful
/datum/controller/subsystem/shuttle/proc/moveShuttle(shuttleId, dockId, timed)
@@ -664,7 +669,7 @@ SUBSYSTEM_DEF(shuttle)
emergencyNoRecall = TRUE
endvote_passed = TRUE
/datum/controller/subsystem/shuttle/proc/action_load(datum/map_template/shuttle/loading_template, obj/docking_port/stationary/destination_port)
/datum/controller/subsystem/shuttle/proc/action_load(datum/map_template/shuttle/loading_template, obj/docking_port/stationary/destination_port, replace = FALSE)
// Check for an existing preview
if(preview_shuttle && (loading_template != preview_template))
preview_shuttle.jumpToNullSpace()
@@ -673,8 +678,8 @@ SUBSYSTEM_DEF(shuttle)
QDEL_NULL(preview_reservation)
if(!preview_shuttle)
if(load_template(loading_template))
preview_shuttle.linkup(loading_template, destination_port)
load_template(loading_template)
// preview_shuttle.linkup(loading_template, destination_port)
preview_template = loading_template
// get the existing shuttle information, if any
@@ -684,7 +689,7 @@ SUBSYSTEM_DEF(shuttle)
if(istype(destination_port))
D = destination_port
else if(existing_shuttle)
else if(existing_shuttle && replace)
timer = existing_shuttle.timer
mode = existing_shuttle.mode
D = existing_shuttle.get_docked()
@@ -703,11 +708,12 @@ SUBSYSTEM_DEF(shuttle)
WARNING("Template shuttle [preview_shuttle] cannot dock at [D] ([result]).")
return
if(existing_shuttle)
if(existing_shuttle && replace)
existing_shuttle.jumpToNullSpace()
var/list/force_memory = preview_shuttle.movement_force
preview_shuttle.movement_force = list("KNOCKDOWN" = 0, "THROW" = 0)
preview_shuttle.mode = SHUTTLE_PREARRIVAL//No idle shuttle moving. Transit dock get removed if shuttle moves too long.
preview_shuttle.initiate_docking(D)
preview_shuttle.movement_force = force_memory
@@ -718,7 +724,7 @@ SUBSYSTEM_DEF(shuttle)
preview_shuttle.timer = timer
preview_shuttle.mode = mode
preview_shuttle.register()
preview_shuttle.register(replace)
// TODO indicate to the user that success happened, rather than just
// blanking the modification tab
@@ -848,7 +854,8 @@ SUBSYSTEM_DEF(shuttle)
return data
/datum/controller/subsystem/shuttle/ui_act(action, params)
if(..())
. = ..()
if(.)
return
var/mob/user = usr
@@ -891,22 +898,10 @@ SUBSYSTEM_DEF(shuttle)
SSblackbox.record_feedback("text", "shuttle_manipulator", 1, "[M.name]")
break
if("preview")
if(S)
. = TRUE
unload_preview()
load_template(S)
if(preview_shuttle)
preview_template = S
user.forceMove(get_turf(preview_shuttle))
if("load")
if(existing_shuttle == backup_shuttle)
// TODO make the load button disabled
WARNING("The shuttle that the selected shuttle will replace \
is the backup shuttle. Backup shuttle is required to be \
intact for round sanity.")
else if(S)
if(S && !shuttle_loading)
. = TRUE
shuttle_loading = TRUE
// If successful, returns the mobile docking port
var/obj/docking_port/mobile/mdp = action_load(S)
if(mdp)
@@ -914,3 +909,38 @@ SUBSYSTEM_DEF(shuttle)
message_admins("[key_name_admin(usr)] loaded [mdp] with the shuttle manipulator.")
log_admin("[key_name(usr)] loaded [mdp] with the shuttle manipulator.</span>")
SSblackbox.record_feedback("text", "shuttle_manipulator", 1, "[mdp.name]")
shuttle_loading = FALSE
if("preview")
//if(preview_shuttle && (loading_template != preview_template))
if(S && !shuttle_loading)
. = TRUE
shuttle_loading = TRUE
unload_preview()
load_template(S)
if(preview_shuttle)
preview_template = S
user.forceMove(get_turf(preview_shuttle))
shuttle_loading = FALSE
if("replace")
if(existing_shuttle == backup_shuttle)
// TODO make the load button disabled
WARNING("The shuttle that the selected shuttle will replace \
is the backup shuttle. Backup shuttle is required to be \
intact for round sanity.")
else if(S && !shuttle_loading)
. = TRUE
shuttle_loading = TRUE
// If successful, returns the mobile docking port
var/obj/docking_port/mobile/mdp = action_load(S, replace = TRUE)
if(mdp)
user.forceMove(get_turf(mdp))
message_admins("[key_name_admin(usr)] load/replaced [mdp] with the shuttle manipulator.")
log_admin("[key_name(usr)] load/replaced [mdp] with the shuttle manipulator.</span>")
SSblackbox.record_feedback("text", "shuttle_manipulator", 1, "[mdp.name]")
shuttle_loading = FALSE
if(emergency == mdp) //you just changed the emergency shuttle, there are events in game + captains that can change your snowflake choice.
var/set_purchase = alert(usr, "Do you want to also disable shuttle purchases/random events that would change the shuttle?", "Butthurt Admin Prevention", "Yes, disable purchases/events", "No, I want to possibly get owned")
if(set_purchase == "Yes, disable purchases/events")
SSshuttle.shuttle_purchased = SHUTTLEPURCHASE_FORCED
+8 -23
View File
@@ -60,15 +60,10 @@ SUBSYSTEM_DEF(stickyban)
/datum/controller/subsystem/stickyban/proc/Populatedbcache()
var/newdbcache = list() //so if we runtime or the db connection dies we don't kill the existing cache
// var/datum/db_query/query_stickybans = SSdbcore.NewQuery("SELECT ckey, reason, banning_admin, datetime FROM [format_table_name("stickyban")] ORDER BY ckey")
// var/datum/db_query/query_ckey_matches = SSdbcore.NewQuery("SELECT stickyban, matched_ckey, first_matched, last_matched, exempt FROM [format_table_name("stickyban_matched_ckey")] ORDER BY first_matched")
// var/datum/db_query/query_cid_matches = SSdbcore.NewQuery("SELECT stickyban, matched_cid, first_matched, last_matched FROM [format_table_name("stickyban_matched_cid")] ORDER BY first_matched")
// var/datum/db_query/query_ip_matches = SSdbcore.NewQuery("SELECT stickyban, INET_NTOA(matched_ip), first_matched, last_matched FROM [format_table_name("stickyban_matched_ip")] ORDER BY first_matched")
var/datum/DBQuery/query_stickybans = SSdbcore.NewQuery("SELECT ckey, reason, banning_admin, datetime FROM [format_table_name("stickyban")] ORDER BY ckey")
var/datum/DBQuery/query_ckey_matches = SSdbcore.NewQuery("SELECT stickyban, matched_ckey, first_matched, last_matched, exempt FROM [format_table_name("stickyban_matched_ckey")] ORDER BY first_matched")
var/datum/DBQuery/query_cid_matches = SSdbcore.NewQuery("SELECT stickyban, matched_cid, first_matched, last_matched FROM [format_table_name("stickyban_matched_cid")] ORDER BY first_matched")
var/datum/DBQuery/query_ip_matches = SSdbcore.NewQuery("SELECT stickyban, INET_NTOA(matched_ip), first_matched, last_matched FROM [format_table_name("stickyban_matched_ip")] ORDER BY first_matched")
var/datum/db_query/query_stickybans = SSdbcore.NewQuery("SELECT ckey, reason, banning_admin, datetime FROM [format_table_name("stickyban")] ORDER BY ckey")
var/datum/db_query/query_ckey_matches = SSdbcore.NewQuery("SELECT stickyban, matched_ckey, first_matched, last_matched, exempt FROM [format_table_name("stickyban_matched_ckey")] ORDER BY first_matched")
var/datum/db_query/query_cid_matches = SSdbcore.NewQuery("SELECT stickyban, matched_cid, first_matched, last_matched FROM [format_table_name("stickyban_matched_cid")] ORDER BY first_matched")
var/datum/db_query/query_ip_matches = SSdbcore.NewQuery("SELECT stickyban, INET_NTOA(matched_ip), first_matched, last_matched FROM [format_table_name("stickyban_matched_ip")] ORDER BY first_matched")
SSdbcore.QuerySelect(list(query_stickybans, query_ckey_matches, query_cid_matches, query_ip_matches))
@@ -161,25 +156,15 @@ SUBSYSTEM_DEF(stickyban)
if (!ban["message"])
ban["message"] = "Evasion"
// TODO: USE NEW DB IMPLEMENTATION
var/datum/DBQuery/query_create_stickyban = SSdbcore.NewQuery(
"INSERT IGNORE INTO [format_table_name("stickyban")] (ckey, reason, banning_admin) VALUES ([ckey], [ban["message"]], ban["admin"]))"
var/datum/db_query/query_create_stickyban = SSdbcore.NewQuery(
"INSERT IGNORE INTO [format_table_name("stickyban")] (ckey, reason, banning_admin) VALUES (:ckey, :message, :admin)",
list("ckey" = ckey, "message" = ban["message"], "admin" = ban["admin"])
)
if (query_create_stickyban.warn_execute())
if (!query_create_stickyban.warn_execute())
qdel(query_create_stickyban)
return
qdel(query_create_stickyban)
// var/datum/db_query/query_create_stickyban = SSdbcore.NewQuery(
// "INSERT IGNORE INTO [format_table_name("stickyban")] (ckey, reason, banning_admin) VALUES (:ckey, :message, :admin)",
// list("ckey" = ckey, "message" = ban["message"], "admin" = ban["admin"])
// )
// if (!query_create_stickyban.warn_execute())
// qdel(query_create_stickyban)
// return
// qdel(query_create_stickyban)
var/list/sqlckeys = list()
var/list/sqlcids = list()
var/list/sqlips = list()
+44 -13
View File
@@ -1,32 +1,63 @@
#define OCCLUSION_DISTANCE 20
/datum/sun
var/azimuth = 0 // clockwise, top-down rotation from 0 (north) to 359
var/power_mod = 1 // how much power this sun is outputting relative to standard
/datum/sun/vv_edit_var(var_name, var_value)
. = ..()
if(var_name == NAMEOF(src, azimuth))
SSsun.complete_movement()
/atom/proc/check_obscured(datum/sun/sun, distance = OCCLUSION_DISTANCE)
var/target_x = round(sin(sun.azimuth), 0.01)
var/target_y = round(cos(sun.azimuth), 0.01)
var/x_hit = x
var/y_hit = y
var/turf/hit
for(var/run in 1 to distance)
x_hit += target_x
y_hit += target_y
hit = locate(round(x_hit, 1), round(y_hit, 1), z)
if(hit.opacity)
return TRUE
if(hit.x == 1 || hit.x == world.maxx || hit.y == 1 || hit.y == world.maxy) //edge of the map
break
return FALSE
SUBSYSTEM_DEF(sun)
name = "Sun"
wait = 1 MINUTES
flags = SS_NO_TICK_CHECK
var/azimuth = 0 ///clockwise, top-down rotation from 0 (north) to 359
var/list/datum/sun/suns = list()
var/datum/sun/primary_sun
var/azimuth_mod = 1 ///multiplier against base_rotation
var/base_rotation = 6 ///base rotation in degrees per fire
/datum/controller/subsystem/sun/Initialize(start_timeofday)
azimuth = rand(0, 359)
primary_sun = new
suns += primary_sun
primary_sun.azimuth = rand(0, 359)
azimuth_mod = round(rand(50, 200)/100, 0.01) // 50% - 200% of standard rotation
if(prob(50))
azimuth_mod *= -1
return ..()
/datum/controller/subsystem/sun/fire(resumed = FALSE)
azimuth += azimuth_mod * base_rotation
azimuth = round(azimuth, 0.01)
if(azimuth >= 360)
azimuth -= 360
if(azimuth < 0)
azimuth += 360
for(var/S in suns)
var/datum/sun/sun = S
sun.azimuth += azimuth_mod * base_rotation
sun.azimuth = round(sun.azimuth, 0.01)
if(sun.azimuth >= 360)
sun.azimuth -= 360
if(sun.azimuth < 0)
sun.azimuth += 360
complete_movement()
/datum/controller/subsystem/sun/proc/complete_movement()
SEND_SIGNAL(src, COMSIG_SUN_MOVED, azimuth)
SEND_SIGNAL(src, COMSIG_SUN_MOVED, primary_sun, suns)
/datum/controller/subsystem/sun/vv_edit_var(var_name, var_value)
. = ..()
if(var_name == NAMEOF(src, azimuth))
complete_movement()
#undef OCCLUSION_DISTANCE
+11 -6
View File
@@ -69,6 +69,7 @@ SUBSYSTEM_DEF(ticker)
var/modevoted = FALSE //Have we sent a vote for the gamemode?
var/station_integrity = 100 // stored at roundend for use in some antag goals
var/emergency_reason
/datum/controller/subsystem/ticker/Initialize(timeofday)
load_mode()
@@ -268,7 +269,7 @@ SUBSYSTEM_DEF(ticker)
if(!GLOB.Debug2)
if(!can_continue)
log_game("[mode.name] failed pre_setup, cause: [mode.setup_error]")
send2irc("SSticker", "[mode.name] failed pre_setup, cause: [mode.setup_error]")
send2adminchat("SSticker", "[mode.name] failed pre_setup, cause: [mode.setup_error]")
message_admins("<span class='notice'>[mode.name] failed pre_setup, cause: [mode.setup_error]</span>")
QDEL_NULL(mode)
to_chat(world, "<B>Error setting up [GLOB.master_mode].</B> Reverting to pre-game lobby.")
@@ -334,7 +335,7 @@ SUBSYSTEM_DEF(ticker)
var/list/adm = get_admin_counts()
var/list/allmins = adm["present"]
send2irc("Server", "Round [GLOB.round_id ? "#[GLOB.round_id]:" : "of"] [hide_mode ? "secret":"[mode.name]"] has started[allmins.len ? ".":" with no active admins online!"]")
send2adminchat("Server", "Round [GLOB.round_id ? "#[GLOB.round_id]:" : "of"] [hide_mode ? "secret":"[mode.name]"] has started[allmins.len ? ".":" with no active admins online!"]")
setup_done = TRUE
for(var/i in GLOB.start_landmarks_list)
@@ -563,7 +564,10 @@ SUBSYSTEM_DEF(ticker)
if(STATION_DESTROYED_NUKE)
news_message = "We would like to reassure all employees that the reports of a Syndicate backed nuclear attack on [station_name()] are, in fact, a hoax. Have a secure day!"
if(STATION_EVACUATED)
news_message = "The crew of [station_name()] has been evacuated amid unconfirmed reports of enemy activity."
if(emergency_reason)
news_message = "[station_name()] has been evacuated after transmitting the following distress beacon:\n\n[emergency_reason]"
else
news_message = "The crew of [station_name()] has been evacuated amid unconfirmed reports of enemy activity."
if(BLOB_WIN)
news_message = "[station_name()] was overcome by an unknown biological outbreak, killing all crew on board. Don't let it happen to you! Remember, a clean work station is a safe work station."
if(BLOB_NUKE)
@@ -589,7 +593,7 @@ SUBSYSTEM_DEF(ticker)
if(WIZARD_KILLED)
news_message = "Tensions have flared with the Space Wizard Federation following the death of one of their members aboard [station_name()]."
if(STATION_NUKED)
news_message = "[station_name()] activated its self destruct device for unknown reasons. Attempts to clone the Captain so he can be arrested and executed are underway."
news_message = "[station_name()] activated its self-destruct device for unknown reasons. Attempts to clone the Captain so he can be arrested and executed are underway."
if(CLOCK_SUMMON)
news_message = "The garbled messages about hailing a mouse and strange energy readings from [station_name()] have been discovered to be an ill-advised, if thorough, prank by a clown."
if(CLOCK_SILICONS)
@@ -604,9 +608,10 @@ SUBSYSTEM_DEF(ticker)
if(SSblackbox.first_death)
var/list/ded = SSblackbox.first_death
if(ded.len)
news_message += " NT Sanctioned Psykers picked up faint traces of someone near the station, allegedly having had died. Their name was: [ded["name"]], [ded["role"]], at [ded["area"]].[ded["last_words"] ? " Their last words were: \"[ded["last_words"]]\"" : ""]"
var/last_words = ded["last_words"] ? " Their last words were: \"[ded["last_words"]]\"" : ""
news_message += "\nNT Sanctioned Psykers picked up faint traces of someone near the station, allegedly having had died. Their name was: [ded["name"]], [ded["role"]], at [ded["area"]].[last_words]"
else
news_message += " NT Sanctioned Psykers proudly confirm reports that nobody died this shift!"
news_message += "\nNT Sanctioned Psykers proudly confirm reports that nobody died this shift!"
if(news_message)
send2otherserver(news_source, news_message,"News_Report")
+12
View File
@@ -68,6 +68,10 @@ SUBSYSTEM_DEF(vote)
//get the highest number of votes
var/greatest_votes = 0
var/total_votes = 0
if(mode == "gamemode" && CONFIG_GET(flag/must_be_readied_to_vote_gamemode))
for(var/mob/dead/new_player/P in GLOB.player_list)
if(P.ready != PLAYER_READY_TO_PLAY && voted[P.ckey])
choices[choices[voted[P.ckey]]]--
for(var/option in choices)
var/votes = choices[option]
total_votes += votes
@@ -101,6 +105,10 @@ SUBSYSTEM_DEF(vote)
/datum/controller/subsystem/vote/proc/calculate_condorcet_votes(var/blackbox_text)
// https://en.wikipedia.org/wiki/Schulze_method#Implementation
if((mode == "gamemode" || mode == "dynamic") && CONFIG_GET(flag/must_be_readied_to_vote_gamemode))
for(var/mob/dead/new_player/P in GLOB.player_list)
if(P.ready != PLAYER_READY_TO_PLAY && voted[P.ckey])
voted -= P.ckey
var/list/d[][] = new/list(choices.len,choices.len) // the basic vote matrix, how many times a beats b
for(var/ckey in voted)
var/list/this_vote = voted[ckey]
@@ -147,6 +155,10 @@ SUBSYSTEM_DEF(vote)
for(var/choice in choices)
scores_by_choice += "[choice]"
scores_by_choice["[choice]"] = list()
if((mode == "gamemode" || mode == "dynamic") && CONFIG_GET(flag/must_be_readied_to_vote_gamemode))
for(var/mob/dead/new_player/P in GLOB.player_list)
if(P.ready != PLAYER_READY_TO_PLAY && voted[P.ckey])
voted -= P.ckey
for(var/ckey in voted)
var/list/this_vote = voted[ckey]
var/list/pretty_vote = list()
@@ -0,0 +1,149 @@
///Datum that handles
/datum/achievement_data
///Ckey of this achievement data's owner
var/owner_ckey
///Up to date list of all achievements and their info.
var/data = list()
///Original status of achievement.
var/original_cached_data = list()
///Have we done our set-up yet?
var/initialized = FALSE
/datum/achievement_data/New(ckey)
owner_ckey = ckey
if(SSachievements.initialized && !initialized)
InitializeData()
/datum/achievement_data/proc/InitializeData()
initialized = TRUE
load_all_achievements() //So we know which achievements we have unlocked so far.
///Gets list of changed rows in MassInsert format
/datum/achievement_data/proc/get_changed_data()
. = list()
for(var/T in data)
var/datum/award/A = SSachievements.awards[T]
if(data[T] != original_cached_data[T])//If our data from before is not the same as now, save it to db.
var/deets = A.get_changed_rows(owner_ckey,data[T])
if(deets)
. += list(deets)
/datum/achievement_data/proc/load_all_achievements()
set waitfor = FALSE
var/list/kv = list()
var/datum/db_query/Query = SSdbcore.NewQuery(
"SELECT achievement_key,value FROM [format_table_name("achievements")] WHERE ckey = :ckey",
list("ckey" = owner_ckey)
)
if(!Query.Execute())
qdel(Query)
return
while(Query.NextRow())
var/key = Query.item[1]
var/value = text2num(Query.item[2])
kv[key] = value
qdel(Query)
for(var/T in subtypesof(/datum/award))
var/datum/award/A = SSachievements.awards[T]
if(!A || !A.name) //Skip abstract achievements types
continue
if(!data[T])
data[T] = A.parse_value(kv[A.database_id])
original_cached_data[T] = data[T]
///Updates local cache with db data for the given achievement type if it wasn't loaded yet.
/datum/achievement_data/proc/get_data(achievement_type)
var/datum/award/A = SSachievements.awards[achievement_type]
if(!A.name)
return FALSE
if(!data[achievement_type])
data[achievement_type] = A.load(owner_ckey)
original_cached_data[achievement_type] = data[achievement_type]
///Unlocks an achievement of a specific type. achievement type is a typepath to the award, user is the mob getting the award, and value is an optional value to be used for defining a score to add to the leaderboard
/datum/achievement_data/proc/unlock(achievement_type, mob/user, value = 1)
set waitfor = FALSE
if(!SSachievements.achievements_enabled)
return
var/datum/award/A = SSachievements.awards[achievement_type]
get_data(achievement_type) //Get the current status first if necessary
if(istype(A, /datum/award/achievement))
if(data[achievement_type]) //You already unlocked it so don't bother running the unlock proc
return
data[achievement_type] = TRUE
A.on_unlock(user) //Only on default achievement, as scores keep going up.
else if(istype(A, /datum/award/score))
data[achievement_type] += value
///Getter for the status/score of an achievement
/datum/achievement_data/proc/get_achievement_status(achievement_type)
return data[achievement_type]
///Resets an achievement to default values.
/datum/achievement_data/proc/reset(achievement_type)
if(!SSachievements.achievements_enabled)
return
var/datum/award/A = SSachievements.awards[achievement_type]
get_data(achievement_type)
if(istype(A, /datum/award/achievement))
data[achievement_type] = FALSE
else if(istype(A, /datum/award/score))
data[achievement_type] = 0
/datum/achievement_data/ui_assets(mob/user)
return list(
get_asset_datum(/datum/asset/spritesheet/simple/achievements),
)
/datum/achievement_data/ui_state(mob/user)
return GLOB.always_state
/datum/achievement_data/ui_interact(mob/user, datum/tgui/ui)
ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
ui = new(user, src, "Achievements")
ui.open()
/datum/achievement_data/ui_data(mob/user)
var/ret_data = list() // screw standards (qustinnus you must rename src.data ok)
ret_data["categories"] = list("Bosses", "Misc", "Mafia", "Scores")
ret_data["achievements"] = list()
ret_data["user_key"] = user.ckey
var/datum/asset/spritesheet/simple/assets = get_asset_datum(/datum/asset/spritesheet/simple/achievements)
//This should be split into static data later
for(var/achievement_type in SSachievements.awards)
if(!SSachievements.awards[achievement_type].name) //No name? we a subtype.
continue
if(isnull(data[achievement_type])) //We're still loading
continue
var/list/this = list(
"name" = SSachievements.awards[achievement_type].name,
"desc" = SSachievements.awards[achievement_type].desc,
"category" = SSachievements.awards[achievement_type].category,
"icon_class" = assets.icon_class_name(SSachievements.awards[achievement_type].icon),
"value" = data[achievement_type],
"score" = ispath(achievement_type,/datum/award/score)
)
ret_data["achievements"] += list(this)
return ret_data
/datum/achievement_data/ui_static_data(mob/user)
. = ..()
.["highscore"] = list()
for(var/score in SSachievements.scores)
var/datum/award/score/S = SSachievements.scores[score]
if(!S.name || !S.track_high_scores || !S.high_scores.len)
continue
.["highscore"] += list(list("name" = S.name,"scores" = S.high_scores))
/client/verb/checkachievements()
set category = "OOC"
set name = "Check achievements"
set desc = "See all of your achievements!"
player_details.achievements.ui_interact(usr)
+117
View File
@@ -0,0 +1,117 @@
/datum/award
///Name of the achievement, If null it won't show up in the achievement browser. (Handy for inheritance trees)
var/name
var/desc = "You did it."
///Found in UI_Icons/Achievements
var/icon = "default"
var/category = "Normal"
///What ID do we use in db, limited to 32 characters
var/database_id
//Bump this up if you're changing outdated table identifier and/or achievement type
var/achievement_version = 2
//Value returned on db connection failure, in case we want to differ 0 and nonexistent later on
var/default_value = FALSE
///This proc loads the achievement data from the hub.
/datum/award/proc/load(key)
if(!SSdbcore.Connect())
return default_value
if(!key || !database_id || !name)
return default_value
var/raw_value = get_raw_value(key)
return parse_value(raw_value)
///This saves the changed data to the hub.
/datum/award/proc/get_changed_rows(key, value)
if(!database_id || !key || !name)
return
return list(
"ckey" = key,
"achievement_key" = database_id,
"value" = value,
)
/datum/award/proc/get_metadata_row()
return list(
"achievement_key" = database_id,
"achievement_version" = achievement_version,
"achievement_type" = "award",
"achievement_name" = name,
"achievement_description" = desc,
)
///Get raw numerical achievement value from the database
/datum/award/proc/get_raw_value(key)
var/datum/db_query/Q = SSdbcore.NewQuery(
"SELECT value FROM [format_table_name("achievements")] WHERE ckey = :ckey AND achievement_key = :achievement_key",
list("ckey" = key, "achievement_key" = database_id)
)
if(!Q.Execute(async = TRUE))
qdel(Q)
return 0
var/result = 0
if(Q.NextRow())
result = text2num(Q.item[1])
qdel(Q)
return result
//Should return sanitized value for achievement cache
/datum/award/proc/parse_value(raw_value)
return default_value
///Can be overriden for achievement specific events
/datum/award/proc/on_unlock(mob/user)
return
///Achievements are one-off awards for usually doing cool things.
/datum/award/achievement
desc = "Achievement for epic people"
/datum/award/achievement/get_metadata_row()
. = ..()
.["achievement_type"] = "achievement"
/datum/award/achievement/parse_value(raw_value)
return raw_value > 0
/datum/award/achievement/on_unlock(mob/user)
. = ..()
to_chat(user, "<span class='greenannounce'><B>Achievement unlocked: [name]!</B></span>")
///Scores are for leaderboarded things, such as killcount of a specific boss
/datum/award/score
desc = "you did it sooo many times."
category = "Scores"
default_value = 0
var/track_high_scores = TRUE
var/list/high_scores = list()
/datum/award/score/New()
. = ..()
if(track_high_scores)
LoadHighScores()
/datum/award/score/get_metadata_row()
. = ..()
.["achievement_type"] = "score"
/datum/award/score/proc/LoadHighScores()
var/datum/db_query/Q = SSdbcore.NewQuery(
"SELECT ckey,value FROM [format_table_name("achievements")] WHERE achievement_key = :achievement_key ORDER BY value DESC LIMIT 50",
list("achievement_key" = database_id)
)
if(!Q.Execute(async = TRUE))
qdel(Q)
return
else
while(Q.NextRow())
var/key = Q.item[1]
var/score = text2num(Q.item[2])
high_scores[key] = score
qdel(Q)
/datum/award/score/parse_value(raw_value)
return isnum(raw_value) ? raw_value : 0
@@ -0,0 +1,130 @@
/datum/award/achievement/boss
category = "Bosses"
icon = "baseboss"
/datum/award/achievement/boss/tendril_exterminator
name = "Tendril Exterminator"
desc = "Watch your step"
database_id = BOSS_MEDAL_TENDRIL
icon = "tendril"
/datum/award/achievement/boss/boss_killer
name = "Boss Killer"
desc = "You've come a long ways from asking how to switch hands."
database_id = "Boss Killer"
// icon = "firstboss"
/datum/award/achievement/boss/blood_miner_kill
name = "Blood-Drunk Miner Killer"
desc = "I guess he couldn't handle his drink that well."
database_id = BOSS_MEDAL_MINER
icon = "miner"
/datum/award/achievement/boss/demonic_miner_kill
name = "Demonic-Frost Miner Killer"
desc = "Definitely harder than the Blood-Drunk Miner."
database_id = BOSS_MEDAL_FROSTMINER
/datum/award/achievement/boss/bubblegum_kill
name = "Bubblegum Killer"
desc = "I guess he wasn't made of candy after all"
database_id = BOSS_MEDAL_BUBBLEGUM
icon = "bbgum"
/datum/award/achievement/boss/colossus_kill
name = "Colossus Killer"
desc = "The bigger they are... the better the loot"
database_id = BOSS_MEDAL_COLOSSUS
icon = "colossus"
/datum/award/achievement/boss/drake_kill
name = "Drake Killer"
desc = "Now I can wear Rune Platebodies!"
database_id = BOSS_MEDAL_DRAKE
icon = "drake"
/datum/award/achievement/boss/hierophant_kill
name = "Hierophant Killer"
desc = "Hierophant, but not triumphant."
database_id = BOSS_MEDAL_HIEROPHANT
icon = "hierophant"
/datum/award/achievement/boss/legion_kill
name = "Legion Killer"
desc = "We were many..now we are none."
database_id = BOSS_MEDAL_LEGION
icon = "legion"
/datum/award/achievement/boss/swarmer_beacon_kill
name = "Swarm Beacon Killer"
desc = "GET THEM OFF OF ME!"
database_id = BOSS_MEDAL_SWARMERS
icon = "swarmer"
/datum/award/achievement/boss/wendigo_kill
name = "Wendigo Killer"
desc = "You've now ruined years of mythical storytelling."
database_id = BOSS_MEDAL_WENDIGO
/datum/award/achievement/boss/blood_miner_crusher
name = "Blood-Drunk Miner Crusher"
desc = "I guess he couldn't handle his drink that well."
database_id = BOSS_MEDAL_MINER_CRUSHER
icon = "miner"
/datum/award/achievement/boss/demonic_miner_crusher
name = "Demonic-Frost Miner Crusher"
desc = "Definitely harder than the Blood-Drunk Miner."
database_id = BOSS_MEDAL_FROSTMINER_CRUSHER
/datum/award/achievement/boss/bubblegum_crusher
name = "Bubblegum Crusher"
desc = "I guess he wasn't made of candy after all"
database_id = BOSS_MEDAL_BUBBLEGUM_CRUSHER
icon = "bbgum"
/datum/award/achievement/boss/colossus_crusher
name = "Colossus Crusher"
desc = "The bigger they are... the better the loot"
database_id = BOSS_MEDAL_COLOSSUS_CRUSHER
icon = "colossus"
/datum/award/achievement/boss/drake_crusher
name = "Drake Crusher"
desc = "Now I can wear Rune Platebodies!"
database_id = BOSS_MEDAL_DRAKE_CRUSHER
icon = "drake"
/datum/award/achievement/boss/hierophant_crusher
name = "Hierophant Crusher"
desc = "Hierophant, but not triumphant."
database_id = BOSS_MEDAL_HIEROPHANT_CRUSHER
icon = "hierophant"
/datum/award/achievement/boss/legion_crusher
name = "Legion Crusher"
desc = "We were many... now we are none."
database_id = BOSS_MEDAL_LEGION_CRUSHER
/datum/award/achievement/boss/swarmer_beacon_crusher
name = "Swarm Beacon Crusher"
desc = "GET THEM OFF OF ME!"
database_id = BOSS_MEDAL_SWARMERS_CRUSHER
/datum/award/achievement/boss/wendigo_crusher
name = "Wendigo Crusher"
desc = "You've now ruined years of mythical storytelling."
database_id = BOSS_MEDAL_WENDIGO_CRUSHER
//should be removed soon
// /datum/award/achievement/boss/king_goat_kill
// name = "King Goat Killer"
// desc = "The king is dead, long live the king!"
// database_id = BOSS_MEDAL_KINGGOAT
// icon = "goatboss"
// /datum/award/achievement/boss/king_goat_crusher
// name = "King Goat Crusher"
// desc = "The king is dead, long live the king!"
// database_id = BOSS_MEDAL_KINGGOAT_CRUSHER
// icon = "goatboss"
+54
View File
@@ -0,0 +1,54 @@
/datum/award/score/tendril_score
name = "Tendril Score"
desc = "Watch your step"
database_id = TENDRIL_CLEAR_SCORE
/datum/award/score/boss_score
name = "Bosses Killed"
desc = "You've killed HOW many?"
database_id = BOSS_SCORE
/datum/award/score/blood_miner_score
name = "Blood-Drunk Miners Killed"
desc = "You've killed HOW many?"
database_id = MINER_SCORE
/datum/award/score/demonic_miner_score
name = "Demonic-Frost Miners Killed"
desc = "You've killed HOW many?"
database_id = FROST_MINER_SCORE
/datum/award/score/bubblegum_score
name = "Bubblegums Killed"
desc = "You've killed HOW many?"
database_id = BUBBLEGUM_SCORE
/datum/award/score/colussus_score
name = "Colossus Killed"
desc = "You've killed HOW many?"
database_id = COLOSSUS_SCORE
/datum/award/score/drake_score
name = "Drakes Killed"
desc = "You've killed HOW many?"
database_id = DRAKE_SCORE
/datum/award/score/hierophant_score
name = "Hierophants Killed"
desc = "You've killed HOW many?"
database_id = HIEROPHANT_SCORE
/datum/award/score/legion_score
name = "Legions Killed"
desc = "You've killed HOW many?"
database_id = LEGION_SCORE
/datum/award/score/swarmer_beacon_score
name = "Swarmer Beacons Killed"
desc = "You've killed HOW many?"
database_id = SWARMER_BEACON_SCORE
/datum/award/score/wendigo_score
name = "Wendigos Killed"
desc = "You've killed HOW many?"
database_id = WENDIGO_SCORE
@@ -0,0 +1,115 @@
/datum/award/achievement/mafia
category = "Mafia"
icon = "basemafia"
///ALL THE ACHIEVEMENTS FOR WINNING A ROUND AS A ROLE///
/datum/award/achievement/mafia/assistant
name = "Assistant Victory"
desc = "If you got killed instead of someone more important, you just flexed the true strength of your \"\"\"\"role\"\"\"\"."
database_id = MAFIA_MEDAL_ASSISTANT
icon = "town"
/datum/award/achievement/mafia/detective
name = "Detective Victory"
desc = "If you did this with a Medical Doctor in the game, i'm not really that impressed."
database_id = MAFIA_MEDAL_DETECTIVE
icon = "town"
/datum/award/achievement/mafia/psychologist
name = "Psychologist Victory"
desc = "You learned how to not reveal someone random night one! Or... maybe you're just a lucky bastard."
database_id = MAFIA_MEDAL_PSYCHOLOGIST
icon = "town"
/datum/award/achievement/mafia/chaplain
name = "Chaplain Victory"
desc = "Useless... until the one night the thoughtfeeder confidently claims themselves as detective. Mafia's true bullshit detector."
database_id = MAFIA_MEDAL_CHAPLAIN
icon = "town"
/datum/award/achievement/mafia/md
name = "Medical Doctor Victory"
desc = "Congratulations on learning how to not talk!"
database_id = MAFIA_MEDAL_MD
icon = "town"
/datum/award/achievement/mafia/officer
name = "Security Officer Victory"
desc = "Don't worry, you can win this if you're dead! You... did use your ability to become dead, right?"
database_id = MAFIA_MEDAL_OFFICER
icon = "town"
/datum/award/achievement/mafia/lawyer
name = "Lawyer Victory"
desc = "Oh don't mind me, i'm just the worst rol- Oops, I just instantly ended the game."
database_id = MAFIA_MEDAL_LAWYER
icon = "town"
/datum/award/achievement/mafia/hop
name = "Head of Personnel Victory"
desc = "King of Assistants, waster of a single mafia's night, thrower of games."
database_id = MAFIA_MEDAL_HOP
icon = "town"
/datum/award/achievement/mafia/warden
name = "Warden Victory"
desc = "Make changelings think you're detective, go on lockdown, actual detective investigates you and dies. Cha cha real smooth!"
database_id = MAFIA_MEDAL_WARDEN
icon = "town"
/datum/award/achievement/mafia/hos
name = "Head of Security Victory"
desc = "Certified not shitcurity."
database_id = MAFIA_MEDAL_HOS
icon = "town"
/datum/award/achievement/mafia/changeling
name = "Changeling Victory"
desc = "I think the changelings are metacomming."
database_id = MAFIA_MEDAL_CHANGELING
icon = "mafia"
/datum/award/achievement/mafia/thoughtfeeder
name = "Thoughtfeeder Victory"
desc = "Clown's best friend. And Obsessed. And fugitive? Whose side are you on?!"
database_id = MAFIA_MEDAL_THOUGHTFEEDER
icon = "mafia"
/datum/award/achievement/mafia/traitor
name = "Traitor Victory"
desc = "Guys, we still have two more changelings to ki-!! TRAITOR VICTORY !!"
database_id = MAFIA_MEDAL_TRAITOR
icon = "neutral"
/datum/award/achievement/mafia/nightmare
name = "Nightmare Victory"
desc = "DID YOUR LIGHT FLICKER?!"
database_id = MAFIA_MEDAL_NIGHTMARE
icon = "neutral"
/datum/award/achievement/mafia/fugitive
name = "Fugitive Victory"
desc = "I'm just the description on an achievement, but if you end up having to choose between town and changelings, go changelings."
database_id = MAFIA_MEDAL_FUGITIVE
icon = "neutral"
/datum/award/achievement/mafia/obsessed
name = "Obsessed Victory"
desc = "You got your target lynched, so instead of being spiteful and annoying, you're just smug and annoying."
database_id = MAFIA_MEDAL_OBSESSED
icon = "neutral"
/datum/award/achievement/mafia/clown
name = "Clown Victory"
desc = "Did you know this works on traitors, despite their immunity? If you hit the jackpot and manage to kill one, they'll salt into the next dimension. Clown tips!"
database_id = MAFIA_MEDAL_CLOWN
icon = "neutral"
///ALL THE ACHIEVEMENTS FOR MISC MAFIA ODDITIES///
/datum/award/achievement/mafia/universally_hated
name = "Universally Hated"
desc = "Managed to get more than 12 votes when put up on trial, jesus christ."
database_id = MAFIA_MEDAL_HATED
icon = "hated"
@@ -0,0 +1,161 @@
/datum/award/achievement/misc
category = "Misc"
icon = "basemisc"
/datum/award/achievement/misc/meteor_examine
name = "Your Life Before Your Eyes"
desc = "Take a close look at hurtling space debris"
database_id = MEDAL_METEOR
icon = "meteors"
/datum/award/achievement/misc/pulse
name = "Jackpot"
desc = "Win a pulse rifle from an arcade machine"
database_id = MEDAL_PULSE
icon = "jackpot"
/datum/award/achievement/misc/time_waste
name = "Time waster"
desc = "Speak no evil, hear no evil, see just errors"
database_id = MEDAL_TIMEWASTE
icon = "timewaste"
/datum/award/achievement/misc/feat_of_strength
name = "Feat of Strength"
desc = "If the rod is immovable, is it passing you or are you passing it?"
database_id = MEDAL_RODSUPLEX
icon = "featofstrength"
/datum/award/achievement/misc/round_and_full
name = "Round and Full"
desc = "Well at least you aren't down the river, I hear they eat people there."
database_id = MEDAL_CLOWNCARKING
icon = "clownking"
/datum/award/achievement/misc/the_best_driver
name = "The Best Driver"
desc = "100 honks later"
database_id = MEDAL_THANKSALOT
icon = "clownthanks"
/datum/award/achievement/misc/helbitaljanken
name = "Helbitaljanken"
desc = "You janked hard"
database_id = MEDAL_HELBITALJANKEN
icon = "helbital"
/datum/award/achievement/misc/getting_an_upgrade
name = "Getting an upgrade"
desc = "Make your first unique material item!"
database_id = MEDAL_MATERIALCRAFT
/datum/award/achievement/misc/rocket_holdup
name = "Disk, Please!"
desc = "Is the man currently pointing a loaded rocket launcher at your head point blank really dumb enough to pull the trigger? Do you really want to find out?"
database_id = MEDAL_DISKPLEASE
/datum/award/achievement/misc/gamer
name = "My Watchlist Status is Not Important"
desc = "You may be under the impression that violent video games are a harmless pastime, but the security and medical personnel swarming your location with batons and knockout gas look like they disagree."
database_id = MEDAL_GAMER
/datum/award/achievement/misc/vendor_squish
name = "I Was a Teenage Anarchist"
desc = "You were doing a great job sticking it to the system until that vending machine decided to fight back."
database_id = MEDAL_VENDORSQUISH
/datum/award/achievement/misc/swirlie
name = "A Bowl-d New World"
desc = "There's a lot of grisly ways to kick it on the Spinward Periphery, but drowning to death in a toilet probably wasn't what you had in mind. Probably."
database_id = MEDAL_SWIRLIE
/datum/award/achievement/misc/selfouch
name = "How Do I Switch Hands???"
desc = "If you saw someone casually club themselves upside the head with a toolbox anywhere in the galaxy but here, you'd probably be pretty concerned for them."
database_id = MEDAL_SELFOUCH
/datum/award/achievement/misc/sandman
name = "Mister Sandman"
desc = "Mechanically speaking, there's no real benefit to being unconscious during surgery. Weird how insistent this doctor is about using the N2O anyway though, huh?"
database_id = MEDAL_SANDMAN
/datum/award/achievement/misc/cleanboss
name = "One Lean, Mean, Cleaning Machine"
desc = "How does it feel to know that your workplace values a mop bucket on wheels more than you?" // i can do better than this give me time
database_id = MEDAL_CLEANBOSS
/datum/award/achievement/misc/rule8
name = "Rule 8"
desc = "Call an admin this is ILLEGAL!!"
database_id = MEDAL_RULE8
icon = "rule8"
/datum/award/achievement/misc/speed_round
name = "Long shift"
desc = "Well, that didn't take long."
database_id = MEDAL_LONGSHIFT
icon = "longshift"
/datum/award/achievement/misc/snail
name = "KKKiiilll mmmeee"
desc = "You were a little too ambitious, but hey, I guess you're still alive?"
database_id = MEDAL_SNAIL
icon = "snail"
/datum/award/achievement/misc/lookoutsir
name = "Look Out, Sir!"
desc = "Either awarded for making the ultimate sacrifice for your comrades, or a really dumb attempt at grenade jumping."
database_id = MEDAL_LOOKOUTSIR
/datum/award/achievement/misc/gottem
name = "HA, GOTTEM"
desc = "Made you look!"
database_id = MEDAL_GOTTEM
/datum/award/achievement/misc/ascension
name = "Ascension"
desc = "Caedite eos. Novit enim Dominus qui sunt eius."
database_id = MEDAL_ASCENSION
icon = "ascension"
/datum/award/achievement/misc/frenching
name = "Frenching"
desc = "Just a taste, for science!"
database_id = MEDAL_FRENCHING
icon = "frenching"
/datum/award/achievement/misc/ash_ascension
name = "Nightwatcher's Eyes"
desc = "You've risen above the flames, became one with the ashes. You've been reborn as one with the Nightwatcher."
database_id = MEDAL_ASH_ASCENSION
icon = "ashascend"
/datum/award/achievement/misc/flesh_ascension
name = "Vortex of Arms"
desc = "You've became something more, something greater. A piece of the emperor resides within you, and you within him."
database_id = MEDAL_FLESH_ASCENSION
icon = "fleshascend"
/datum/award/achievement/misc/rust_ascension
name = "Hills of Rust"
desc = "You've summoned a piece of the Hill of rust, and so the Hills welcome you."
database_id = MEDAL_RUST_ASCENSION
icon = "rustascend"
/datum/award/achievement/misc/void_ascension
name = "All that perish"
desc = "Place of a different being, different time. Everything ends there... but maybe it is just the beginning?"
database_id = MEDAL_VOID_ASCENSION
icon = "voidascend"
/datum/award/achievement/misc/toolbox_soul
name = "SOUL'd Out"
desc = "My eternal soul was destroyed to make a toolbox look funny and all I got was this achievement..."
database_id = MEDAL_TOOLBOX_SOUL
icon = "toolbox_soul"
/datum/award/achievement/misc/chemistry_tut
name = "Perfect chemistry blossom"
desc = "Passed the chemistry tutorial with perfect purity!"
database_id = MEDAL_CHEM_TUT
icon = "chem_tut"
+11
View File
@@ -0,0 +1,11 @@
///How many times did we survive being a cripple?
/datum/award/score/hardcore_random
name = "Hardcore random points"
desc = "Well, I might be a blind, deaf, crippled guy, but hey, at least I'm alive."
database_id = HARDCORE_RANDOM_SCORE
///How many maintenance pills did you eat?
/datum/award/score/maintenance_pill
name = "Maintenance Pills Consumed"
desc = "Wait why?"
database_id = MAINTENANCE_PILL_SCORE
@@ -0,0 +1,10 @@
/datum/award/achievement/skill
category = "Skills"
icon = "baseskill"
/datum/award/achievement/skill/legendary_miner
name = "Legendary miner"
desc = "No mere rock can stop me!"
database_id = MEDAL_LEGENDARY_MINER
icon = "mining"
+88 -88
View File
@@ -1,53 +1,53 @@
/**
*# Callback Datums
*A datum that holds a proc to be called on another object, used to track proccalls to other objects
*
* ## USAGE
*
* ```
* var/datum/callback/C = new(object|null, /proc/type/path|"procstring", arg1, arg2, ... argn)
* var/timerid = addtimer(C, time, timertype)
* you can also use the compiler define shorthand
* var/timerid = addtimer(CALLBACK(object|null, /proc/type/path|procstring, arg1, arg2, ... argn), time, timertype)
* ```
*
* Note: proc strings can only be given for datum proc calls, global procs must be proc paths
*
* Also proc strings are strongly advised against because they don't compile error if the proc stops existing
*
* In some cases you can provide a shortform of the procname, see the proc typepath shortcuts documentation below
*
* ## INVOKING THE CALLBACK
*`var/result = C.Invoke(args, to, add)` additional args are added after the ones given when the callback was created
*
* `var/result = C.InvokeAsync(args, to, add)` Asyncronous - returns . on the first sleep then continues on in the background
* after the sleep/block ends, otherwise operates normally.
*
* ## PROC TYPEPATH SHORTCUTS
* (these operate on paths, not types, so to these shortcuts, datum is NOT a parent of atom, etc...)
*
* ### global proc while in another global proc:
* .procname
*
* `CALLBACK(GLOBAL_PROC, .some_proc_here)`
*
* ### proc defined on current(src) object (when in a /proc/ and not an override) OR overridden at src or any of it's parents:
* .procname
*
* `CALLBACK(src, .some_proc_here)`
*
* ### when the above doesn't apply:
*.proc/procname
*
* `CALLBACK(src, .proc/some_proc_here)`
*
*
* proc defined on a parent of a some type
*
* `/some/type/.proc/some_proc_here`
*
* Otherwise you must always provide the full typepath of the proc (/type/of/thing/proc/procname)
*/
*# Callback Datums
*A datum that holds a proc to be called on another object, used to track proccalls to other objects
*
* ## USAGE
*
* ```
* var/datum/callback/C = new(object|null, /proc/type/path|"procstring", arg1, arg2, ... argn)
* var/timerid = addtimer(C, time, timertype)
* you can also use the compiler define shorthand
* var/timerid = addtimer(CALLBACK(object|null, /proc/type/path|procstring, arg1, arg2, ... argn), time, timertype)
* ```
*
* Note: proc strings can only be given for datum proc calls, global procs must be proc paths
*
* Also proc strings are strongly advised against because they don't compile error if the proc stops existing
*
* In some cases you can provide a shortform of the procname, see the proc typepath shortcuts documentation below
*
* ## INVOKING THE CALLBACK
*`var/result = C.Invoke(args, to, add)` additional args are added after the ones given when the callback was created
*
* `var/result = C.InvokeAsync(args, to, add)` Asyncronous - returns . on the first sleep then continues on in the background
* after the sleep/block ends, otherwise operates normally.
*
* ## PROC TYPEPATH SHORTCUTS
* (these operate on paths, not types, so to these shortcuts, datum is NOT a parent of atom, etc...)
*
* ### global proc while in another global proc:
* .procname
*
* `CALLBACK(GLOBAL_PROC, .some_proc_here)`
*
* ### proc defined on current(src) object (when in a /proc/ and not an override) OR overridden at src or any of it's parents:
* .procname
*
* `CALLBACK(src, .some_proc_here)`
*
* ### when the above doesn't apply:
*.proc/procname
*
* `CALLBACK(src, .proc/some_proc_here)`
*
*
* proc defined on a parent of a some type
*
* `/some/type/.proc/some_proc_here`
*
* Otherwise you must always provide the full typepath of the proc (/type/of/thing/proc/procname)
*/
/datum/callback
///The object we will be calling the proc on
@@ -60,13 +60,13 @@
var/datum/weakref/user
/**
* Create a new callback datum
*
* Arguments
* * thingtocall the object to call the proc on
* * proctocall the proc to call on the target object
* * ... an optional list of extra arguments to pass to the proc
*/
* Create a new callback datum
*
* Arguments
* * thingtocall the object to call the proc on
* * proctocall the proc to call on the target object
* * ... an optional list of extra arguments to pass to the proc
*/
/datum/callback/New(thingtocall, proctocall, ...)
if (thingtocall)
object = thingtocall
@@ -76,13 +76,13 @@
if(usr)
user = WEAKREF(usr)
/**
* Immediately Invoke proctocall on thingtocall, with waitfor set to false
*
* Arguments:
* * thingtocall Object to call on
* * proctocall Proc to call on that object
* * ... optional list of arguments to pass as arguments to the proc being called
*/
* Immediately Invoke proctocall on thingtocall, with waitfor set to false
*
* Arguments:
* * thingtocall Object to call on
* * proctocall Proc to call on that object
* * ... optional list of arguments to pass as arguments to the proc being called
*/
/world/proc/ImmediateInvokeAsync(thingtocall, proctocall, ...)
set waitfor = FALSE
@@ -97,13 +97,13 @@
call(thingtocall, proctocall)(arglist(calling_arguments))
/**
* Invoke this callback
*
* Calls the registered proc on the registered object, if the user ref
* can be resolved it also inclues that as an arg
*
* If the datum being called on is varedited, the call is wrapped via WrapAdminProcCall
*/
* Invoke this callback
*
* Calls the registered proc on the registered object, if the user ref
* can be resolved it also inclues that as an arg
*
* If the datum being called on is varedited, the call is wrapped via [WrapAdminProcCall][/proc/WrapAdminProcCall]
*/
/datum/callback/proc/Invoke(...)
if(!usr)
var/datum/weakref/W = user
@@ -130,13 +130,13 @@
return call(object, delegate)(arglist(calling_arguments))
/**
* Invoke this callback async (waitfor=false)
*
* Calls the registered proc on the registered object, if the user ref
* can be resolved it also inclues that as an arg
*
* If the datum being called on is varedited, the call is wrapped via WrapAdminProcCall
*/
* Invoke this callback async (waitfor=false)
*
* Calls the registered proc on the registered object, if the user ref
* can be resolved it also inclues that as an arg
*
* If the datum being called on is varedited, the call is wrapped via WrapAdminProcCall
*/
/datum/callback/proc/InvokeAsync(...)
set waitfor = FALSE
@@ -166,7 +166,7 @@
/**
Helper datum for the select callbacks proc
*/
*/
/datum/callback_select
var/list/finished
var/pendingcount
@@ -192,16 +192,16 @@
finished[index] = rtn
/**
* Runs a list of callbacks asyncronously, returning only when all have finished
*
* Callbacks can be repeated, to call it multiple times
*
* Arguments:
* * list/callbacks the list of callbacks to be called
* * list/callback_args the list of lists of arguments to pass into each callback
* * savereturns Optionally save and return the list of returned values from each of the callbacks
* * resolution The number of byond ticks between each time you check if all callbacks are complete
*/
* Runs a list of callbacks asyncronously, returning only when all have finished
*
* Callbacks can be repeated, to call it multiple times
*
* Arguments:
* * list/callbacks the list of callbacks to be called
* * list/callback_args the list of lists of arguments to pass into each callback
* * savereturns Optionally save and return the list of returned values from each of the callbacks
* * resolution The number of byond ticks between each time you check if all callbacks are complete
*/
/proc/callback_select(list/callbacks, list/callback_args, savereturns = TRUE, resolution = 1)
if (!callbacks)
return
+1 -1
View File
@@ -73,7 +73,7 @@
/datum/component/butchering/proc/Butcher(mob/living/butcher, mob/living/meat)
var/meat_quality = 50 + (bonus_modifier/10) //increases through quality of butchering tool, and through if it was butchered in the kitchen or not
if(istype(get_area(butcher), /area/crew_quarters/kitchen))
if(istype(get_area(butcher), /area/service/kitchen))
meat_quality = meat_quality + 10
var/turf/T = meat.drop_location()
var/final_effectiveness = effectiveness - meat.butcher_difficulty
@@ -45,8 +45,8 @@
/datum/crafting_recipe/bloodsucker/blackcoffin
name = "Black Coffin"
result = /obj/structure/closet/crate/coffin/blackcoffin
tools = list(/obj/item/weldingtool,
/obj/item/screwdriver)
tools = list(TOOL_WELDER,
TOOL_SCREWDRIVER)
reqs = list(/obj/item/stack/sheet/cloth = 1,
/obj/item/stack/sheet/mineral/wood = 5,
/obj/item/stack/sheet/metal = 1)
@@ -72,8 +72,8 @@
/datum/crafting_recipe/bloodsucker/metalcoffin
name = "Metal Coffin"
result =/obj/structure/closet/crate/coffin/metalcoffin
tools = list(/obj/item/weldingtool,
/obj/item/screwdriver)
tools = list(TOOL_WELDER,
TOOL_SCREWDRIVER)
reqs = list(/obj/item/stack/sheet/metal = 5)
time = 100
subcategory = CAT_FURNITURE
@@ -84,9 +84,9 @@
name = "Persuasion Rack"
//desc = "For converting crewmembers into loyal Vassals."
result = /obj/structure/bloodsucker/vassalrack
tools = list(/obj/item/weldingtool,
//obj/item/screwdriver,
/obj/item/wrench
tools = list(TOOL_WELDER,
//TOOL_SCREWDRIVER,
TOOL_WRENCH
)
reqs = list(/obj/item/stack/sheet/mineral/wood = 3,
/obj/item/stack/sheet/metal = 2,
@@ -108,8 +108,8 @@
name = "Candelabrum"
//desc = "For converting crewmembers into loyal Vassals."
result = /obj/structure/bloodsucker/candelabrum
tools = list(/obj/item/weldingtool,
/obj/item/wrench
tools = list(TOOL_WELDER,
TOOL_WRENCH
)
reqs = list(/obj/item/stack/sheet/metal = 3,
/obj/item/stack/rods = 1,
@@ -336,6 +336,18 @@
subcategory = CAT_TOOL
category = CAT_MISC
/datum/crafting_recipe/blackmarket_uplink
name = "Black Market Uplink"
result = /obj/item/blackmarket_uplink
time = 20
tools = list(TOOL_SCREWDRIVER, TOOL_WIRECUTTER)
reqs = list(/obj/item/stock_parts/subspace/amplifier = 1,
/obj/item/stack/cable_coil = 15,
/obj/item/radio = 1,
/obj/item/analyzer = 1)
subcategory = CAT_MISCELLANEOUS
category = CAT_MISC
/datum/crafting_recipe/heretic/codex
name = "Codex Cicatrix"
result = /obj/item/forbidden_book
@@ -16,7 +16,7 @@
/datum/crafting_recipe/strobeshield
name = "Strobe Shield"
result = /obj/item/assembly/flash/shield
result = /obj/item/shield/riot/flash
reqs = list(/obj/item/wallframe/flasher = 1,
/obj/item/assembly/flash/handheld = 1,
/obj/item/shield/riot = 1)
+4 -3
View File
@@ -26,15 +26,16 @@ GLOBAL_LIST_EMPTY(GPS_list)
if(. == COMPONENT_INCOMPATIBLE || !isitem(parent))
return COMPONENT_INCOMPATIBLE
var/atom/A = parent
A.add_overlay("working")
if(starton)
A.add_overlay("working")
else
tracking = FALSE
A.name = "[initial(A.name)] ([gpstag])"
RegisterSignal(parent, COMSIG_ITEM_ATTACK_SELF, .proc/interact)
if(!emp_proof)
RegisterSignal(parent, COMSIG_ATOM_EMP_ACT, .proc/on_emp_act)
RegisterSignal(parent, COMSIG_PARENT_EXAMINE, .proc/on_examine)
RegisterSignal(parent, COMSIG_CLICK_ALT, .proc/on_AltClick)
if(!starton)
tracking = FALSE
///Called on COMSIG_ITEM_ATTACK_SELF
/datum/component/gps/item/proc/interact(datum/source, mob/user)
+148 -29
View File
@@ -10,25 +10,37 @@
*/
/datum/component/material_container
/// The total amount of materials this material container contains
var/total_amount = 0
/// The maximum amount of materials this material container can contain
var/max_amount
var/sheet_type
/// Map of material ref -> amount
var/list/materials //Map of key = material ref | Value = amount
/// The list of materials that this material container can accept
var/list/allowed_materials
var/show_on_examine
var/disable_attackby
var/list/allowed_typecache
/// The last main material that was inserted into this container
var/last_inserted_id
/// Whether or not this material container allows specific amounts from sheets to be inserted
var/precise_insertion = FALSE
/// A callback invoked before materials are inserted into this container
var/datum/callback/precondition
/// A callback invoked after materials are inserted into this container
var/datum/callback/after_insert
/// Sets up the proper signals and fills the list of materials with the appropriate references.
/datum/component/material_container/Initialize(list/mat_list, max_amt = 0, _show_on_examine = FALSE, list/allowed_types, datum/callback/_precondition, datum/callback/_after_insert, _disable_attackby)
if(!isatom(parent))
return COMPONENT_INCOMPATIBLE
materials = list()
max_amount = max(0, max_amt)
show_on_examine = _show_on_examine
disable_attackby = _disable_attackby
allowed_materials = mat_list || list()
if(allowed_types)
if(ispath(allowed_types) && allowed_types == /obj/item/stack)
allowed_typecache = GLOB.typecache_stack
@@ -38,14 +50,32 @@
precondition = _precondition
after_insert = _after_insert
RegisterSignal(parent, COMSIG_PARENT_ATTACKBY, .proc/OnAttackBy)
RegisterSignal(parent, COMSIG_PARENT_EXAMINE, .proc/OnExamine)
RegisterSignal(parent, COMSIG_PARENT_ATTACKBY, .proc/on_attackby)
RegisterSignal(parent, COMSIG_PARENT_EXAMINE, .proc/on_examine)
for(var/mat in mat_list) //Make the assoc list ref | amount
var/datum/material/M = SSmaterials.GetMaterialRef(mat)
materials[M] = 0
for(var/mat in mat_list) //Make the assoc list material reference -> amount
var/mat_ref = SSmaterials.GetMaterialRef(mat)
if(isnull(mat_ref))
continue
var/mat_amt = mat_list[mat]
if(isnull(mat_amt))
mat_amt = 0
materials[mat_ref] += mat_amt
/datum/component/material_container/Destroy(force, silent)
materials = null
allowed_typecache = null
// if(insertion_check)
// QDEL_NULL(insertion_check)
if(precondition)
QDEL_NULL(precondition)
if(after_insert)
QDEL_NULL(after_insert)
return ..()
/datum/component/material_container/proc/on_examine(datum/source, mob/user, list/examine_list)
SIGNAL_HANDLER
/datum/component/material_container/proc/OnExamine(datum/source, mob/user, list/examine_list)
if(show_on_examine)
for(var/I in materials)
var/datum/material/M = I
@@ -54,7 +84,9 @@
examine_list += "<span class='notice'>It has [amt] units of [lowertext(M.name)] stored.</span>"
/// Proc that allows players to fill the parent with mats
/datum/component/material_container/proc/OnAttackBy(datum/source, obj/item/I, mob/living/user)
/datum/component/material_container/proc/on_attackby(datum/source, obj/item/I, mob/living/user)
SIGNAL_HANDLER
var/list/tc = allowed_typecache
if(disable_attackby)
return
@@ -63,48 +95,104 @@
if(I.item_flags & ABSTRACT)
return
if((I.flags_1 & HOLOGRAM_1) || (I.item_flags & NO_MAT_REDEMPTION) || (tc && !is_type_in_typecache(I, tc)))
// if(!(mat_container_flags & MATCONTAINER_SILENT))
to_chat(user, "<span class='warning'>[parent] won't accept [I]!</span>")
return
. = COMPONENT_NO_AFTERATTACK
var/datum/callback/pc = precondition
if(pc && !pc.Invoke(user))
return
var/material_amount = get_item_material_amount(I)
var/material_amount = get_item_material_amount(I) //, mat_container_flags)
if(!material_amount)
to_chat(user, "<span class='warning'>[I] does not contain sufficient materials to be accepted by [parent].</span>")
return
if((!precise_insertion || !GLOB.typecache_stack[I.type]) && !has_space(material_amount))
to_chat(user, "<span class='warning'>[parent] has not enough space. Please remove materials from [parent] in order to insert more.</span>")
to_chat(user, "<span class='warning'>[parent] is full. Please remove materials from [parent] in order to insert more.</span>")
return
user_insert(I, user)
user_insert(I, user) //, mat_container_flags)
/// Proc used for when player inserts materials
/datum/component/material_container/proc/user_insert(obj/item/I, mob/living/user)
/datum/component/material_container/proc/user_insert(obj/item/I, mob/living/user, datum/component/remote_materials/remote = null)
set waitfor = FALSE
var/requested_amount
var/active_held = user.get_active_held_item() // differs from I when using TK
if(istype(I, /obj/item/stack) && precise_insertion)
var/atom/current_parent = parent
var/inserted = 0
//handle stacks specially
if(istype(I, /obj/item/stack))
var/atom/current_parent = remote ? remote.parent : parent //is the user using a remote materials component?
var/obj/item/stack/S = I
requested_amount = input(user, "How much do you want to insert?", "Inserting [S.singular_name]s") as num|null
//try to get ammount to use
var/requested_amount
if(precise_insertion)
requested_amount = input(user, "How much do you want to insert?", "Inserting [S.singular_name]s") as num|null
else
requested_amount= S.amount
if(isnull(requested_amount) || (requested_amount <= 0))
return
if(QDELETED(I) || QDELETED(user) || QDELETED(src) || parent != current_parent || user.physical_can_use_topic(current_parent) < UI_INTERACTIVE || user.get_active_held_item() != active_held)
if(QDELETED(I) || QDELETED(user) || QDELETED(src) || user.get_active_held_item() != active_held)
return
if(!user.temporarilyRemoveItemFromInventory(I))
to_chat(user, "<span class='warning'>[I] is stuck to you and cannot be placed into [parent].</span>")
return
var/inserted = insert_item(I, stack_amt = requested_amount)
//are we still in range after the user input?
if((remote ? remote.parent : parent) != current_parent || user.physical_can_use_topic(current_parent) < UI_INTERACTIVE)
return
inserted = insert_stack(S, requested_amount)
else
if(!user.temporarilyRemoveItemFromInventory(I))
to_chat(user, "<span class='warning'>[I] is stuck to you and cannot be placed into [parent].</span>")
return
inserted = insert_item(I)
qdel(I)
if(inserted)
to_chat(user, "<span class='notice'>You insert a material total of [inserted] into [parent].</span>")
qdel(I)
if(after_insert)
after_insert.Invoke(I, last_inserted_id, inserted)
else if(I == active_held)
user.put_in_active_hand(I)
if(remote && remote.after_insert)
remote.after_insert.Invoke(I, last_inserted_id, inserted)
//Inserts a number of sheets from a stack, returns the amount of sheets used.
/datum/component/material_container/proc/insert_stack(obj/item/stack/S, amt, multiplier = 1)
if(isnull(amt))
amt = S.amount
if(amt <= 0)
return FALSE
if(amt > S.amount)
amt = S.amount
var/material_amt = get_item_material_amount(S)
if(!material_amt)
return FALSE
//get max number of sheets we have room to add
var/mat_per_sheet = material_amt/S.amount
amt = min(amt, round((max_amount - total_amount) / (mat_per_sheet)))
if(!amt)
return FALSE
//add the mats and keep track of how much was added
var/starting_total = total_amount
for(var/MAT in materials)
materials[MAT] += S.mats_per_unit[MAT] * amt * multiplier
total_amount += S.mats_per_unit[MAT] * amt * multiplier
var/total_added = total_amount - starting_total
//update last_inserted_id with mat making up majority of the stack
var/primary_mat
var/max_mat_value = 0
for(var/MAT in materials)
if(S.mats_per_unit[MAT] > max_mat_value)
max_mat_value = S.mats_per_unit[MAT]
primary_mat = MAT
last_inserted_id = primary_mat
S.use(amt)
return total_added
/// Proc specifically for inserting items, returns the amount of materials entered.
/datum/component/material_container/proc/insert_item(obj/item/I, var/multiplier = 1, stack_amt)
/datum/component/material_container/proc/insert_item(obj/item/I, var/multiplier = 1)
if(QDELETED(I))
return FALSE
@@ -117,16 +205,46 @@
last_inserted_id = insert_item_materials(I, multiplier)
return material_amount
/**
* Inserts the relevant materials from an item into this material container.
*
* Arguments:
* - [source][/obj/item]: The source of the materials we are inserting.
* - multiplier: The multiplier for the materials being inserted.
* - breakdown_flags: The breakdown bitflags that will be used to retrieve the materials from the source
*/
/datum/component/material_container/proc/insert_item_materials(obj/item/I, multiplier = 1)
var/primary_mat
var/max_mat_value = 0
for(var/MAT in materials)
materials[MAT] += I.custom_materials[MAT] * multiplier
total_amount += I.custom_materials[MAT] * multiplier
if(I.custom_materials[MAT] > max_mat_value)
var/list/item_materials = I.custom_materials
for(var/MAT in item_materials)
if(!can_hold_material(MAT))
continue
materials[MAT] += item_materials[MAT] * multiplier
total_amount += item_materials[MAT] * multiplier
if(item_materials[MAT] > max_mat_value)
max_mat_value = item_materials[MAT]
primary_mat = MAT
return primary_mat
/**
* The default check for whether we can add materials to this material container.
*
* Arguments:
* - [mat][/atom/material]: The material we are checking for insertability.
*/
/datum/component/material_container/proc/can_hold_material(datum/material/mat)
if(mat in allowed_typecache)
return TRUE
if(istype(mat) && ((mat.id in allowed_typecache) || (mat.type in allowed_materials)))
allowed_materials += mat // This could get messy with passing lists by ref... but if you're doing that the list expansion is probably being taken care of elsewhere anyway...
return TRUE
// if(insertion_check?.Invoke(mat))
// allowed_materials += mat
// return TRUE
return FALSE
/// For inserting an amount of material
/datum/component/material_container/proc/insert_amount_mat(amt, var/datum/material/mat)
if(!istype(mat))
@@ -135,6 +253,7 @@
var/total_amount_saved = total_amount
if(mat)
materials[mat] += amt
total_amount += amt
else
for(var/i in materials)
materials[i] += amt
+6
View File
@@ -33,6 +33,8 @@
RegisterSignal(parent, COMSIG_LIVING_REVIVE, .proc/on_revive)
RegisterSignal(parent, COMSIG_MOB_HUD_CREATED, .proc/modify_hud)
RegisterSignal(parent, COMSIG_MOB_DEATH, .proc/stop_processing)
RegisterSignal(parent, COMSIG_VOID_MASK_ACT, .proc/direct_sanity_drain)
if(owner.hud_used)
modify_hud()
@@ -377,6 +379,10 @@
remove_temp_moods()
setSanity(initial(sanity))
///Causes direct drain of someone's sanity, call it with a numerical value corresponding how badly you want to hurt their sanity
/datum/component/mood/proc/direct_sanity_drain(datum/source, amount)
setSanity(sanity + amount)
#undef ECSTATIC_SANITY_PEN
#undef SLIGHT_INSANITY_PEN
#undef MINOR_INSANITY_PEN
+2
View File
@@ -146,9 +146,11 @@
if(!istype(A) || !get_turf(A) || A == src)
return
orbit_target = A
return A.AddComponent(/datum/component/orbiter, src, radius, clockwise, rotation_speed, rotation_segments, pre_rotation)
/atom/movable/proc/stop_orbit(datum/component/orbiter/orbits)
orbit_target = null
return // We're just a simple hook
/atom/proc/transfer_observers_to(atom/target)
+5
View File
@@ -275,6 +275,11 @@
else
target.visible_message("<span class='danger'>[target] is hit by a [proj_name][hit_part ? " in the [hit_part.name]" : ""]!</span>", null, null, COMBAT_MESSAGE_RANGE, target)
to_chat(target, "<span class='userdanger'>You're hit by a [proj_name][hit_part ? " in the [hit_part.name]" : ""]!</span>")
for(var/M in purple_hearts)
var/mob/living/martyr = M
if(martyr.stat == DEAD && martyr.client)
martyr.client.give_award(/datum/award/achievement/misc/lookoutsir, martyr)
UnregisterSignal(parent, COMSIG_PARENT_PREQDELETED)
if(queued_delete)
qdel(parent)
+14 -13
View File
@@ -15,13 +15,15 @@ handles linking back and forth.
var/category
var/allow_standalone
var/local_size = INFINITY
var/datum/callback/after_insert
/datum/component/remote_materials/Initialize(category, mapload, allow_standalone = TRUE, force_connect = FALSE)
/datum/component/remote_materials/Initialize(category, mapload, allow_standalone = TRUE, force_connect = FALSE, _after_insert)
if (!isatom(parent))
return COMPONENT_INCOMPATIBLE
src.category = category
src.allow_standalone = allow_standalone
after_insert = _after_insert
RegisterSignal(parent, COMSIG_PARENT_ATTACKBY, .proc/OnAttackBy)
@@ -67,7 +69,7 @@ handles linking back and forth.
/datum/material/plastic,
)
mat_container = parent.AddComponent(/datum/component/material_container, allowed_mats, local_size, allowed_types=/obj/item/stack)
mat_container = parent.AddComponent(/datum/component/material_container, allowed_mats, local_size, allowed_types=/obj/item/stack, _after_insert = after_insert)
/datum/component/remote_materials/proc/set_local_size(size)
local_size = size
@@ -84,38 +86,37 @@ handles linking back and forth.
_MakeLocal()
/datum/component/remote_materials/proc/OnAttackBy(datum/source, obj/item/I, mob/user)
if (istype(I, /obj/item/multitool))
var/obj/item/multitool/M = I
if (!QDELETED(M.buffer) && istype(M.buffer, /obj/machinery/ore_silo))
if (silo == M.buffer)
if(I.tool_behaviour == TOOL_MULTITOOL)
if((I.buffer) && istype(I.buffer, /obj/machinery/ore_silo))
if(silo == I.buffer)
to_chat(user, "<span class='notice'>[parent] is already connected to [silo].</span>")
return COMPONENT_NO_AFTERATTACK
if (silo)
if(silo)
silo.connected -= src
silo.updateUsrDialog()
else if (mat_container)
else if(mat_container)
mat_container.retrieve_all()
qdel(mat_container)
silo = M.buffer
silo = I.buffer
silo.connected += src
silo.updateUsrDialog()
mat_container = silo.GetComponent(/datum/component/material_container)
to_chat(user, "<span class='notice'>You connect [parent] to [silo] from the multitool's buffer.</span>")
return COMPONENT_NO_AFTERATTACK
else if (silo && istype(I, /obj/item/stack))
if (silo.remote_attackby(parent, user, I))
else if(silo && istype(I, /obj/item/stack))
if(silo.remote_attackby(parent, user, I, src))
return COMPONENT_NO_AFTERATTACK
/datum/component/remote_materials/proc/on_hold()
return silo && silo.holds["[get_area(parent)]/[category]"]
/datum/component/remote_materials/proc/silo_log(obj/machinery/M, action, amount, noun, list/mats)
if (silo)
if(silo)
silo.silo_log(M || parent, action, amount, noun, mats)
/datum/component/remote_materials/proc/format_amount()
if (mat_container)
if(mat_container)
return "[mat_container.total_amount] / [mat_container.max_amount == INFINITY ? "Unlimited" : mat_container.max_amount] ([silo ? "remote" : "local"])"
else
return "0 / 0"
+1 -1
View File
@@ -37,7 +37,7 @@
qdel(src)
/datum/component/riding/proc/vehicle_mob_buckle(datum/source, mob/living/M, force)
handle_vehicle_offsets()
handle_vehicle_offsets(M.buckled?.dir)
/datum/component/riding/proc/handle_vehicle_layer(dir)
var/atom/movable/AM = parent
+1 -1
View File
@@ -125,7 +125,7 @@
else
if(!default_can_user_rotate(user, default_rotation_direction))
return
if(istype(I,/obj/item/wrench))
if(I.tool_behaviour == TOOL_WRENCH)
BaseRot(user,default_rotation_direction)
return COMPONENT_NO_AFTERATTACK

Some files were not shown because too many files have changed in this diff Show More