diff --git a/SQL/database_changelog.txt b/SQL/database_changelog.txt index e142f20cfab..9835d792f6e 100644 --- a/SQL/database_changelog.txt +++ b/SQL/database_changelog.txt @@ -1,3 +1,16 @@ +16th April 2016 + +Added ipintel table, only required if ip intel is enabled in the config + +CREATE TABLE `ipintel` ( +`ip` INT UNSIGNED NOT NULL , +`date` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP NOT NULL , +`intel` REAL NOT NULL DEFAULT '0', +PRIMARY KEY ( `ip` ) +) ENGINE = INNODB; + +--------------------------------------------------- + 21 September 2015, by Jordie0608 Modified table 'poll_question', adding columns 'createdby_ckey', 'createdby_ip' and 'for_trialmin' to bring it inline with the schema used by the tg servers. diff --git a/SQL/tgstation_schema.sql b/SQL/tgstation_schema.sql index 34d83537256..72d08af90f7 100644 --- a/SQL/tgstation_schema.sql +++ b/SQL/tgstation_schema.sql @@ -364,5 +364,14 @@ CREATE TABLE `notes` ( ) ENGINE=InnoDB DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; - +DROP TABLE IF EXISTS `ipintel`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `ipintel` ( +`ip` INT UNSIGNED NOT NULL , +`date` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP NOT NULL , +`intel` REAL NOT NULL DEFAULT '0', +PRIMARY KEY ( `ip` ) +) ENGINE = INNODB; +/*!40101 SET character_set_client = @saved_cs_client */; -- Dump completed on 2013-03-24 18:02:35 diff --git a/SQL/tgstation_schema_prefixed.sql b/SQL/tgstation_schema_prefixed.sql index fe40d2f72cf..ab49b337cf3 100644 --- a/SQL/tgstation_schema_prefixed.sql +++ b/SQL/tgstation_schema_prefixed.sql @@ -359,5 +359,14 @@ CREATE TABLE `SS13_notes` ( ) ENGINE=InnoDB DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; - +DROP TABLE IF EXISTS `SS13_ipintel`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `SS13_ipintel` ( +`ip` INT UNSIGNED NOT NULL , +`date` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP NOT NULL , +`intel` REAL NOT NULL DEFAULT '0', +PRIMARY KEY ( `ip` ) +) ENGINE = INNODB; +/*!40101 SET character_set_client = @saved_cs_client */; -- Dump completed on 2013-03-24 18:02:35 diff --git a/code/controllers/configuration.dm b/code/controllers/configuration.dm index 67e4e70422e..f8aec416e19 100644 --- a/code/controllers/configuration.dm +++ b/code/controllers/configuration.dm @@ -70,6 +70,13 @@ var/forbid_singulo_possession = 0 var/useircbot = 0 + //IP Intel vars + var/ipintel_email + var/ipintel_rating_bad = 1 + var/ipintel_save_good = 12 + var/ipintel_save_bad = 1 + var/ipintel_domain = "check.getipintel.net" + var/admin_legacy_system = 0 //Defines whether the server uses the legacy admin system with admins.txt or the SQL system. Config option in config.txt var/ban_legacy_system = 0 //Defines whether the server uses the legacy banning system with the files in /data or the SQL system. Config option in config.txt var/use_age_restriction_for_jobs = 0 //Do jobs use account age restrictions? --requires database @@ -389,6 +396,17 @@ config.notify_new_player_age = text2num(value) if("irc_first_connection_alert") config.irc_first_connection_alert = 1 + if("ipintel_email") + if (value != "ch@nge.me") + config.ipintel_email = value + if("ipintel_rating_bad") + config.ipintel_rating_bad = text2num(value) + if("ipintel_domain") + config.ipintel_domain = value + if("ipintel_save_good") + config.ipintel_save_good = text2num(value) + if("ipintel_save_bad") + config.ipintel_save_bad = text2num(value) if("aggressive_changelog") config.aggressive_changelog = 1 if("log_runtimes") diff --git a/code/controllers/subsystem/ipintel.dm b/code/controllers/subsystem/ipintel.dm new file mode 100644 index 00000000000..b1b99d59392 --- /dev/null +++ b/code/controllers/subsystem/ipintel.dm @@ -0,0 +1,19 @@ +var/datum/subsystem/ipintel/SSipintel + +/datum/subsystem/ipintel + name = "XKeyScore" + init_order = -10 + flags = SS_NO_FIRE + var/enabled = 0 //disable at round start to avoid checking reconnects + var/throttle = 0 + var/errors = 0 + + var/list/cache = list() + +/datum/subsystem/ipintel/New() + NEW_SS_GLOBAL(SSipintel) + +/datum/subsystem/ipintel/Initialize(timeofday, zlevel) + enabled = 1 + . = ..() + diff --git a/code/game/gamemodes/clock_cult/clock_cult.dm b/code/game/gamemodes/clock_cult/clock_cult.dm index a56ada0193f..8260ff5ff90 100644 --- a/code/game/gamemodes/clock_cult/clock_cult.dm +++ b/code/game/gamemodes/clock_cult/clock_cult.dm @@ -83,8 +83,14 @@ This file's folder contains: M << "You must not involve yourself in other affairs, but... this one... you see it all. Your world glows a brilliant yellow, and all it once it comes to you. \ Ratvar, the Clockwork Justiciar, lies derelict and forgotten in an unseen realm." var/mob/living/simple_animal/drone/D = M - D.update_drone_hack(TRUE, TRUE) - D.languages_spoken |= HUMAN + if(!is_eligible_servant(M)) + if(!silent && !M.stat) + D.visible_message("[M] whirs as it resists an outside influence!") + M << "Corrupt data purged. Resetting repair processor to factory defaults... complete." + return 0 + else + D.update_drone_hack(TRUE, TRUE) + D.languages_spoken |= HUMAN else if(!silent) M << "Your world glows a brilliant yellow! All at once it comes to you. Ratvar, the Clockwork Justiciar, lies in exile, derelict and forgotten in an unseen realm." diff --git a/code/game/gamemodes/clock_cult/clock_items.dm b/code/game/gamemodes/clock_cult/clock_items.dm index c3f7db74900..75051b889f6 100644 --- a/code/game/gamemodes/clock_cult/clock_items.dm +++ b/code/game/gamemodes/clock_cult/clock_items.dm @@ -132,19 +132,11 @@ return 1 /obj/item/clockwork/slab/proc/recite_scripture(mob/living/user) - var/servants = 0 - var/unconverted_ai_exists = FALSE - for(var/mob/living/M in living_mob_list) - if(is_servant_of_ratvar(M)) - servants++ - for(var/mob/living/silicon/ai/ai in living_mob_list) - if(!is_servant_of_ratvar(ai) && ai.client) - unconverted_ai_exists = TRUE var/list/tiers_of_scripture = list("Drivers") - tiers_of_scripture += "Scripts[ratvar_awakens || (servants >= 5 && clockwork_caches >= 1) || no_cost ? "" : " \[LOCKED\]"]" - tiers_of_scripture += "Applications[ratvar_awakens || (servants >= 8 && clockwork_caches >= 3 && clockwork_construction_value >= 50) || no_cost ? "" : " \[LOCKED\]"]" - tiers_of_scripture += "Revenant[ratvar_awakens || (servants >= 10 && clockwork_construction_value >= 100) || no_cost ? "" : " \[LOCKED\]"]" - tiers_of_scripture += "Judgement[ratvar_awakens || (servants >= 10 && clockwork_construction_value >= 100 && !unconverted_ai_exists) || no_cost ? "" : " \[LOCKED\]"]" + tiers_of_scripture += "Scripts[ratvar_awakens || scripture_unlock_check(SCRIPTURE_SCRIPT) || no_cost ? "" : " \[LOCKED\]"]" + tiers_of_scripture += "Applications[ratvar_awakens || scripture_unlock_check(SCRIPTURE_APPLICATION) || no_cost ? "" : " \[LOCKED\]"]" + tiers_of_scripture += "Revenant[ratvar_awakens || scripture_unlock_check(SCRIPTURE_REVENANT) || no_cost ? "" : " \[LOCKED\]"]" + tiers_of_scripture += "Judgement[ratvar_awakens || scripture_unlock_check(SCRIPTURE_JUDGEMENT) || no_cost ? "" : " \[LOCKED\]"]" var/scripture_tier = input(user, "Choose a category of scripture to recite.", "[src]") as null|anything in tiers_of_scripture if(!scripture_tier || !user.canUseTopic(src)) return 0 @@ -187,11 +179,15 @@ /obj/item/clockwork/slab/proc/show_stats(mob/living/user) //A bit barebones, but there really isn't any more needed var/servants = 0 + var/validservants = 0 for(var/mob/living/L in living_mob_list) if(is_servant_of_ratvar(L)) servants++ + if(ishuman(L) || issilicon(L)) + validservants++ user << "State of the Enlightened" user << "Total servants: [servants]" + user << "Servants valid for scripture unlock: [validservants]" user << "Total construction value: [clockwork_construction_value]" user << "Total tinkerer's caches: [clockwork_caches]" user << "Total tinkerer's daemons: [clockwork_daemons] ([servants / 5 < clockwork_daemons ? "DISABLED: Too few servants (5 servants per daemon)!" : "Functioning Normally"])" diff --git a/code/game/gamemodes/clock_cult/clock_scripture.dm b/code/game/gamemodes/clock_cult/clock_scripture.dm index 0e302f4ae51..2621c2689ea 100644 --- a/code/game/gamemodes/clock_cult/clock_scripture.dm +++ b/code/game/gamemodes/clock_cult/clock_scripture.dm @@ -1162,7 +1162,7 @@ Judgement: 10 servants, 100 CV, and any existing AIs are converted or destroyed L.fully_heal() L.stun_absorption = TRUE L.status_flags |= GODMODE - animate(invoker, color = initial(invoker.color), time = 150, easing = EASE_IN) + animate(L, color = initial(L.color), time = 150, easing = EASE_IN) affected_servants += L sleep(150) for(var/mob/living/L in affected_servants) diff --git a/code/game/gamemodes/clock_cult/clock_unsorted.dm b/code/game/gamemodes/clock_cult/clock_unsorted.dm index b688ac714cd..e8fc69c171b 100644 --- a/code/game/gamemodes/clock_cult/clock_unsorted.dm +++ b/code/game/gamemodes/clock_cult/clock_unsorted.dm @@ -20,27 +20,6 @@ qdel(F) return 1 -/proc/generate_cache_component(specific_component_id) //generates a component in the global component cache, either random based on lowest or a specific component - if(specific_component_id) - clockwork_component_cache[specific_component_id]++ - else - var/component_to_generate = get_weighted_component_id() - clockwork_component_cache[component_to_generate]++ - -/proc/get_weighted_component_id(obj/item/clockwork/slab/storage_slab) //returns a chosen component id based on the lowest amount of that component - if(storage_slab) - return pickweight(list("belligerent_eye" = max(MAX_COMPONENTS_BEFORE_RAND - LOWER_PROB_PER_COMPONENT*(clockwork_component_cache["belligerent_eye"] + storage_slab.stored_components["belligerent_eye"]), 1), \ - "vanguard_cogwheel" = max(MAX_COMPONENTS_BEFORE_RAND - LOWER_PROB_PER_COMPONENT*(clockwork_component_cache["vanguard_cogwheel"] + storage_slab.stored_components["vanguard_cogwheel"]), 1), \ - "guvax_capacitor" = max(MAX_COMPONENTS_BEFORE_RAND - LOWER_PROB_PER_COMPONENT*(clockwork_component_cache["guvax_capacitor"] + storage_slab.stored_components["guvax_capacitor"]), 1), \ - "replicant_alloy" = max(MAX_COMPONENTS_BEFORE_RAND - LOWER_PROB_PER_COMPONENT*(clockwork_component_cache["replicant_alloy"] + storage_slab.stored_components["replicant_alloy"]), 1), \ - "hierophant_ansible" = max(MAX_COMPONENTS_BEFORE_RAND - LOWER_PROB_PER_COMPONENT*(clockwork_component_cache["hierophant_ansible"] + storage_slab.stored_components["hierophant_ansible"]), 1))) - - return pickweight(list("belligerent_eye" = max(MAX_COMPONENTS_BEFORE_RAND - LOWER_PROB_PER_COMPONENT*clockwork_component_cache["belligerent_eye"], 1), \ - "vanguard_cogwheel" = max(MAX_COMPONENTS_BEFORE_RAND - LOWER_PROB_PER_COMPONENT*clockwork_component_cache["vanguard_cogwheel"], 1), \ - "guvax_capacitor" = max(MAX_COMPONENTS_BEFORE_RAND - LOWER_PROB_PER_COMPONENT*clockwork_component_cache["guvax_capacitor"], 1), \ - "replicant_alloy" = max(MAX_COMPONENTS_BEFORE_RAND - LOWER_PROB_PER_COMPONENT*clockwork_component_cache["replicant_alloy"], 1), \ - "hierophant_ansible" = max(MAX_COMPONENTS_BEFORE_RAND - LOWER_PROB_PER_COMPONENT*clockwork_component_cache["hierophant_ansible"], 1))) - //allows a mob to select a target to gate to /atom/movable/proc/procure_gateway(mob/living/invoker, time_duration, gateway_uses, two_way) var/list/possible_targets = list() @@ -92,6 +71,53 @@ S2.visible_message("The air in front of [target] ripples before suddenly tearing open!") return 1 +/proc/scripture_unlock_check(scripture_tier) //check if the selected scripture tier is unlocked + var/servants = 0 + var/unconverted_ai_exists = FALSE + for(var/mob/living/M in living_mob_list) + if(is_servant_of_ratvar(M) && (ishuman(M) || issilicon(M))) + servants++ + for(var/mob/living/silicon/ai/ai in living_mob_list) + if(!is_servant_of_ratvar(ai) && ai.client) + unconverted_ai_exists = TRUE + switch(scripture_tier) + if(SCRIPTURE_DRIVER) + return 1 + if(SCRIPTURE_SCRIPT) + if(servants >= 5 && clockwork_caches) + return 1 //5 or more non-brain servants and any number of clockwork caches + if(SCRIPTURE_APPLICATION) + if(servants >= 8 && clockwork_caches >= 3 && clockwork_construction_value >= 50) + return 1 //8 or more non-brain servants, 3+ clockwork caches, and at least 50 CV + if(SCRIPTURE_REVENANT) + if(servants >= 10 && clockwork_caches >= 3 && clockwork_construction_value >= 100) + return 1 //10 or more non-brain servants, 3+ clockwork caches, and at least 100 CV + if(SCRIPTURE_JUDGEMENT) + if(servants >= 10 && clockwork_caches >= 3 && clockwork_construction_value >= 100 && !unconverted_ai_exists) + return 1 //10 or more non-brain servants, 3+ clockwork caches, at least 100 CV, and there are no living, non-servant ais + return 0 + +/proc/generate_cache_component(specific_component_id) //generates a component in the global component cache, either random based on lowest or a specific component + if(specific_component_id) + clockwork_component_cache[specific_component_id]++ + else + var/component_to_generate = get_weighted_component_id() + clockwork_component_cache[component_to_generate]++ + +/proc/get_weighted_component_id(obj/item/clockwork/slab/storage_slab) //returns a chosen component id based on the lowest amount of that component + if(storage_slab) + return pickweight(list("belligerent_eye" = max(MAX_COMPONENTS_BEFORE_RAND - LOWER_PROB_PER_COMPONENT*(clockwork_component_cache["belligerent_eye"] + storage_slab.stored_components["belligerent_eye"]), 1), \ + "vanguard_cogwheel" = max(MAX_COMPONENTS_BEFORE_RAND - LOWER_PROB_PER_COMPONENT*(clockwork_component_cache["vanguard_cogwheel"] + storage_slab.stored_components["vanguard_cogwheel"]), 1), \ + "guvax_capacitor" = max(MAX_COMPONENTS_BEFORE_RAND - LOWER_PROB_PER_COMPONENT*(clockwork_component_cache["guvax_capacitor"] + storage_slab.stored_components["guvax_capacitor"]), 1), \ + "replicant_alloy" = max(MAX_COMPONENTS_BEFORE_RAND - LOWER_PROB_PER_COMPONENT*(clockwork_component_cache["replicant_alloy"] + storage_slab.stored_components["replicant_alloy"]), 1), \ + "hierophant_ansible" = max(MAX_COMPONENTS_BEFORE_RAND - LOWER_PROB_PER_COMPONENT*(clockwork_component_cache["hierophant_ansible"] + storage_slab.stored_components["hierophant_ansible"]), 1))) + + return pickweight(list("belligerent_eye" = max(MAX_COMPONENTS_BEFORE_RAND - LOWER_PROB_PER_COMPONENT*clockwork_component_cache["belligerent_eye"], 1), \ + "vanguard_cogwheel" = max(MAX_COMPONENTS_BEFORE_RAND - LOWER_PROB_PER_COMPONENT*clockwork_component_cache["vanguard_cogwheel"], 1), \ + "guvax_capacitor" = max(MAX_COMPONENTS_BEFORE_RAND - LOWER_PROB_PER_COMPONENT*clockwork_component_cache["guvax_capacitor"], 1), \ + "replicant_alloy" = max(MAX_COMPONENTS_BEFORE_RAND - LOWER_PROB_PER_COMPONENT*clockwork_component_cache["replicant_alloy"], 1), \ + "hierophant_ansible" = max(MAX_COMPONENTS_BEFORE_RAND - LOWER_PROB_PER_COMPONENT*clockwork_component_cache["hierophant_ansible"], 1))) + /* The Ratvarian Language diff --git a/code/game/machinery/porta_turret/portable_turret.dm b/code/game/machinery/porta_turret/portable_turret.dm index 0927ef42271..8036ba00401 100644 --- a/code/game/machinery/porta_turret/portable_turret.dm +++ b/code/game/machinery/porta_turret/portable_turret.dm @@ -706,7 +706,6 @@ pixel_x = (dir & 3)? 0 : (dir == 4 ? -24 : 24) pixel_y = (dir & 3)? (dir ==1 ? -24 : 24) : 0 power_change() //Checks power and initial settings - return /obj/machinery/turretid/initialize() //map-placed turrets autolink turrets if(control_area && istext(control_area)) @@ -779,13 +778,7 @@ return user.set_machine(src) - var/loc = src.loc - if (istype(loc, /turf)) - loc = loc:loc - if (!istype(loc, /area)) - user << text("Turret badly positioned - loc.loc is [].", loc) - return - var/area/area = loc + var/area/area = get_area(src) var/t = "" if(src.locked && (!(istype(user, /mob/living/silicon) || IsAdminGhost(user)))) diff --git a/code/modules/admin/IsBanned.dm b/code/modules/admin/IsBanned.dm index 0d1c1c2370b..75eaeab65fb 100644 --- a/code/modules/admin/IsBanned.dm +++ b/code/modules/admin/IsBanned.dm @@ -99,7 +99,6 @@ log_access("Failed Login: [key] [computer_id] [address] - Banned [.["reason"]]") return . - . = ..() //default pager ban stuff if (.) //byond will not trigger isbanned() for "global" host bans, diff --git a/code/modules/admin/ipintel.dm b/code/modules/admin/ipintel.dm new file mode 100644 index 00000000000..d176de32bb5 --- /dev/null +++ b/code/modules/admin/ipintel.dm @@ -0,0 +1,134 @@ +/datum/ipintel + var/ip + var/intel = 0 + var/cache = FALSE + var/cacheminutesago = 0 + var/cachedate = "" + var/cacherealtime = 0 + +/datum/ipintel/New() + cachedate = SQLtime() + cacherealtime = world.realtime + +/datum/ipintel/proc/is_valid() + . = FALSE + if (intel < 0) + return + if (intel <= config.ipintel_rating_bad) + if (world.realtime < cacherealtime+(config.ipintel_save_good*60*60*10)) + return TRUE + else + if (world.realtime < cacherealtime+(config.ipintel_save_bad*60*60*10)) + return TRUE + +/proc/get_ip_intel(ip, bypasscache = FALSE, updatecache = TRUE) + var/datum/ipintel/res = new() + res.ip = ip + . = res + if (!ip || !config.ipintel_email || !SSipintel.enabled) + return + if (!bypasscache) + var/datum/ipintel/cachedintel = SSipintel.cache[ip] + if (cachedintel && cachedintel.is_valid()) + cachedintel.cache = TRUE + return cachedintel + + if (establish_db_connection()) + var/DBQuery/query = dbcon.NewQuery({" + SELECT date, intel, TIMESTAMPDIFF(MINUTE,date,NOW()) + FROM [format_table_name("ipintel")] + WHERE + ip = INET_ATON('[ip]') + AND (( + intel < [config.ipintel_rating_bad] + AND + date + INTERVAL [config.ipintel_save_good] HOUR > NOW() + ) OR ( + intel >= [config.ipintel_rating_bad] + AND + date + INTERVAL [config.ipintel_save_bad] HOUR > NOW() + )) + "}) + query.Execute() + if (query.NextRow()) + res.cache = TRUE + res.cachedate = query.item[1] + res.intel = query.item[2] + res.cacheminutesago = query.item[3] + res.cacherealtime = world.realtime - (query.item[3]*10*60) + SSipintel.cache[ip] = res + return + res.intel = ip_intel_query(ip) + if (updatecache && res.intel >= 0) + SSipintel.cache[ip] = res + if (establish_db_connection()) + var/DBQuery/query = dbcon.NewQuery("INSERT INTO [format_table_name("ipintel")] (ip, intel) VALUES (INET_ATON('[ip]'), [res.intel]) ON DUPLICATE KEY UPDATE intel = VALUES(intel), date = NOW()") + query.Execute() + return + + + +/proc/ip_intel_query(ip, var/retryed=0) + . = -1 //default + if (!ip) + return + if (SSipintel.throttle > world.timeofday) + return + if (!SSipintel.enabled) + return + + var/list/http[] = world.Export("http://[config.ipintel_domain]/check.php?ip=[ip]&contact=[config.ipintel_email]&format=json&flags=f") + + if (http) + var/status = text2num(http["STATUS"]) + + if (status == 200) + var/response = json_decode(file2text(http["CONTENT"])) + if (response) + if (response["status"] == "success") + var/intelnum = text2num(response["result"]) + if (isnum(intelnum)) + return text2num(response["result"]) + else + ipintel_handle_error("Bad intel from server: [response["result"]].", ip, retryed) + if (!retryed) + sleep(25) + return .(ip, 1) + else + ipintel_handle_error("Bad response from server: [response["status"]].", ip, retryed) + if (!retryed) + sleep(25) + return .(ip, 1) + + else if (status == 429) + ipintel_handle_error("Error #429: We have exceeded the rate limit.", ip, 1) + return + else + ipintel_handle_error("Unknown status code: [status].", ip, retryed) + if (!retryed) + sleep(25) + return .(ip, 1) + else + ipintel_handle_error("Unable to connect to API.", ip, retryed) + if (!retryed) + sleep(25) + return .(ip, 1) + + +/proc/ipintel_handle_error(error, ip, retryed) + if (retryed) + SSipintel.errors++ + error += " Could not check [ip]. Disabling IPINTEL for [SSipintel.errors] minute[( SSipintel.errors == 1 ? "" : "s" )]" + SSipintel.throttle = world.timeofday + (10 * 120 * SSipintel.errors) + else + error += " Attempting retry on [ip]." + log_ipintel(error) + +/proc/log_ipintel(text) + log_game("IPINTEL: [text]") + debug_admins("IPINTEL: [text]") + + + + + diff --git a/code/modules/atmospherics/machinery/portable/canister.dm b/code/modules/atmospherics/machinery/portable/canister.dm index 426730f9851..e711fffbfde 100644 --- a/code/modules/atmospherics/machinery/portable/canister.dm +++ b/code/modules/atmospherics/machinery/portable/canister.dm @@ -307,9 +307,10 @@ if(!holding) var/plasma = air_contents.gases["plasma"] var/n2o = air_contents.gases["n2o"] - if(n2o || plasma) - message_admins("[key_name_admin(usr)] (?) (FLW) opened a canister that contains [n2o ? "N2O" : ""][n2o && plasma ? " & " : ""][plasma ? "Plasma" : ""]! (JMP)") - log_admin("[key_name(usr)] opened a canister that contains [n2o ? "N2O" : ""][n2o && plasma ? " & " : ""][plasma ? "Plasma" : ""] at [x], [y], [z]") + var/bz = air_contents.gases["bz"] + if(n2o || plasma || bz) + message_admins("[key_name_admin(usr)] (?) (FLW) opened a canister that contains [n2o ? "N2O" : ""][n2o && plasma ? " & " : ""][plasma ? "Plasma" : ""][(n2o || plasma) && bz ? " & " : ""][bz ? "BZ" : ""]! (JMP)") + log_admin("[key_name(usr)] opened a canister that contains [n2o ? "N2O" : ""][n2o && plasma ? " & " : ""][plasma ? "Plasma" : ""][(n2o || plasma) && bz ? " & " : ""][bz ? "BZ" : ""] at [x], [y], [z]") else logmsg = "Valve was closed by [key_name(usr)], stopping the transfer into \the [holding || "air"].
" investigate_log(logmsg, "atmos") diff --git a/code/modules/client/client_defines.dm b/code/modules/client/client_defines.dm index 78b19cb7198..7443a4a17ed 100644 --- a/code/modules/client/client_defines.dm +++ b/code/modules/client/client_defines.dm @@ -45,6 +45,7 @@ // Used by html_interface module. var/hi_last_pos + var/ip_intel = "Disabled" //datum that controls the displaying and hiding of tooltips var/datum/tooltip/tooltips diff --git a/code/modules/client/client_procs.dm b/code/modules/client/client_procs.dm index a888c025b7e..c9fa33716bf 100644 --- a/code/modules/client/client_procs.dm +++ b/code/modules/client/client_procs.dm @@ -210,6 +210,8 @@ var/next_external_rsc = 0 sync_client_with_db() + check_ip_intel() + send_resources() if(!void) @@ -322,6 +324,15 @@ var/next_external_rsc = 0 var/DBQuery/query_accesslog = dbcon.NewQuery("INSERT INTO `[format_table_name("connection_log")]` (`id`,`datetime`,`serverip`,`ckey`,`ip`,`computerid`) VALUES(null,Now(),'[serverip]','[sql_ckey]','[sql_ip]','[sql_computerid]');") query_accesslog.Execute() +/client/proc/check_ip_intel() + set waitfor = 0 //we sleep when getting the intel, no need to hold up the client connection while we sleep + if (config.ipintel_email) + var/datum/ipintel/res = get_ip_intel(address) + if (res.intel >= config.ipintel_rating_bad) + message_admins("Proxy Detection: [key_name_admin(src)] IP intel rated [res.intel*100]% likely to be a Proxy/VPN.") + ip_intel = res.intel + + /client/proc/add_verbs_from_config() if(config.see_own_notes) verbs += /client/proc/self_notes diff --git a/code/modules/client/preferences_toggles.dm b/code/modules/client/preferences_toggles.dm index 965e60ddc1f..3b61fb2dedb 100644 --- a/code/modules/client/preferences_toggles.dm +++ b/code/modules/client/preferences_toggles.dm @@ -235,7 +235,7 @@ var/global/list/ghost_forms = list("ghost","ghostking","ghostian2","skeleghost", "ghost_blue","ghost_yellow","ghost_green","ghost_pink", \ "ghost_cyan","ghost_dblue","ghost_dred","ghost_dgreen", \ "ghost_dcyan","ghost_grey","ghost_dyellow","ghost_dpink", "ghost_purpleswirl","ghost_funkypurp","ghost_pinksherbert","ghost_blazeit",\ - "ghost_mellow","ghost_rainbow","ghost_camo","ghost_fire") + "ghost_mellow","ghost_rainbow","ghost_camo","ghost_fire", "catghost") /client/proc/pick_form() if(!is_content_unlocked()) alert("This setting is for accounts with BYOND premium only.") diff --git a/code/modules/hydroponics/grown/ambrosia.dm b/code/modules/hydroponics/grown/ambrosia.dm index dc810aba585..ef576982bed 100644 --- a/code/modules/hydroponics/grown/ambrosia.dm +++ b/code/modules/hydroponics/grown/ambrosia.dm @@ -53,21 +53,23 @@ //Ambrosia Gaia /obj/item/seeds/ambrosia/gaia name = "pack of ambrosia gaia seeds" - desc = "These seeds grow into ambrosia gaia, incredibly rare but imbued with breathtaking potential." + desc = "These seeds grow into ambrosia gaia, filled with infinite potential." icon_state = "seed-ambrosia_gaia" species = "ambrosia_gaia" plantname = "Ambrosia Gaia" product = /obj/item/weapon/reagent_containers/food/snacks/grown/ambrosia/gaia mutatelist = list() - reagents_add = list("earthsblood" = 0.4, "nutriment" = 0.2, "vitamin" = 0.1) - rarity = 100 //These are some pretty good plants right here + reagents_add = list("earthsblood" = 0.05, "nutriment" = 0.06, "vitamin" = 0.05) + rarity = 30 //These are some pretty good plants right here oneharvest = TRUE + weed_rate = 4 + weed_chance = 100 /obj/item/weapon/reagent_containers/food/snacks/grown/ambrosia/gaia - name = "ambrosia gaia" - desc = "The bringer of light." + name = "ambrosia gaia branch" + desc = "Eating this makes you immortal." icon_state = "ambrosia_gaia" filling_color = rgb(255, 175, 0) - origin_tech = "biotech=7" + origin_tech = "biotech=6;materials=5" luminosity = 3 seed = /obj/item/seeds/ambrosia/gaia diff --git a/code/modules/hydroponics/grown/berries.dm b/code/modules/hydroponics/grown/berries.dm index d3eaac3ea7c..3ac1d6c11e8 100644 --- a/code/modules/hydroponics/grown/berries.dm +++ b/code/modules/hydroponics/grown/berries.dm @@ -136,50 +136,6 @@ filling_color = "#6495ED" bitesize_mod = 2 -//Cherry Bombs -/obj/item/seeds/cherry/bomb - name = "pack of cherry bomb pits" - desc = "They give you vibes of dread and frustration." - icon_state = "seed-cherry_bomb" - species = "cherry_bomb" - plantname = "Cherry Bomb Tree" - product = /obj/item/weapon/reagent_containers/food/snacks/grown/cherry_bomb - mutatelist = list() - reagents_add = list("nutriment" = 0.1, "sugar" = 0.1, "blackpowder" = 0.1) - rarity = 25 - -/obj/item/weapon/reagent_containers/food/snacks/grown/cherry_bomb - name = "cherry bombs" - desc = "You think you can hear the hissing of a tiny fuse." - icon_state = "cherry_bomb" - filling_color = rgb(20, 20, 20) - seed = /obj/item/seeds/cherry/bomb - bitesize_mod = 2 - -/obj/item/weapon/reagent_containers/food/snacks/grown/cherry_bomb/attack_self(mob/living/user) - var/area/A = get_area(user) - user.visible_message("[user] plucks the stem from [src]!", "You pluck the stem from [src], which begins to hiss loudly!") - message_admins("[user] ([user.key ? user.key : "no key"]) primed a cherry bomb for detonation at [A] ([user.x], [user.y], [user.z]) (JMP)") - log_game("[user] ([user.key ? user.key : "no key"]) primed a cherry bomb for detonation at [A] ([user.x],[user.y],[user.z]).") - prime() - -/obj/item/weapon/reagent_containers/food/snacks/grown/cherry_bomb/burn() - prime() - ..() - -/obj/item/weapon/reagent_containers/food/snacks/grown/cherry_bomb/proc/prime() - icon_state = "cherry_bomb_lit" - playsound(src, 'sound/effects/fuse.ogg', seed.potency, 0) - sleep(30) - if(!src) - return - var/d_strength = round(seed.potency / 100) - var/h_strength = round(seed.potency / 50) - var/l_strength = round(seed.potency / 20) - var/f_strength = l_strength - explosion(get_turf(src), d_strength, h_strength, l_strength, f_strength) - qdel(src) - // Grapes /obj/item/seeds/grape name = "pack of grape seeds" diff --git a/code/modules/hydroponics/grown/misc.dm b/code/modules/hydroponics/grown/misc.dm index 34bb0d6a5d3..4504d1740ba 100644 --- a/code/modules/hydroponics/grown/misc.dm +++ b/code/modules/hydroponics/grown/misc.dm @@ -94,3 +94,44 @@ origin_tech = "combat=6" trash = /obj/item/weapon/gun/projectile/revolver bitesize_mod = 2 + +//Cherry Bombs +/obj/item/seeds/cherry/bomb + name = "pack of cherry bomb pits" + desc = "They give you vibes of dread and frustration." + icon_state = "seed-cherry_bomb" + species = "cherry_bomb" + plantname = "Cherry Bomb Tree" + product = /obj/item/weapon/reagent_containers/food/snacks/grown/cherry_bomb + mutatelist = list() + reagents_add = list("nutriment" = 0.1, "sugar" = 0.1, "blackpowder" = 0.7) + rarity = 60 //See above + +/obj/item/weapon/reagent_containers/food/snacks/grown/cherry_bomb + name = "cherry bombs" + desc = "You think you can hear the hissing of a tiny fuse." + icon_state = "cherry_bomb" + filling_color = rgb(20, 20, 20) + seed = /obj/item/seeds/cherry/bomb + bitesize_mod = 2 + volume = 125 //Gives enough room for the black powder at max potency + +/obj/item/weapon/reagent_containers/food/snacks/grown/cherry_bomb/attack_self(mob/living/user) + var/area/A = get_area(user) + user.visible_message("[user] plucks the stem from [src]!", "You pluck the stem from [src], which begins to hiss loudly!") + message_admins("[user] ([user.key ? user.key : "no key"]) primed a cherry bomb for detonation at [A] ([user.x], [user.y], [user.z]) (JMP)") + log_game("[user] ([user.key ? user.key : "no key"]) primed a cherry bomb for detonation at [A] ([user.x],[user.y],[user.z]).") + prime() + +/obj/item/weapon/reagent_containers/food/snacks/grown/cherry_bomb/burn() + prime() + ..() + +/obj/item/weapon/reagent_containers/food/snacks/grown/cherry_bomb/ex_act(severity) + qdel(src) //Ensuring that it's deleted by its own explosion + +/obj/item/weapon/reagent_containers/food/snacks/grown/cherry_bomb/proc/prime() + icon_state = "cherry_bomb_lit" + playsound(src, 'sound/effects/fuse.ogg', seed.potency, 0) + reagents.chem_temp = 1000 //Sets off the black powder + reagents.handle_reactions() diff --git a/code/modules/hydroponics/hydroponics.dm b/code/modules/hydroponics/hydroponics.dm index ef3645353b8..f3ede6629ea 100644 --- a/code/modules/hydroponics/hydroponics.dm +++ b/code/modules/hydroponics/hydroponics.dm @@ -80,7 +80,7 @@ if(istype(I, /obj/item/weapon/crowbar)) if(using_irrigation) - user << "Unscrew the hoses first!" + user << "Disconnect the hoses first!" else if(default_deconstruction_crowbar(I, 1)) return else @@ -168,10 +168,12 @@ if(waterlevel <= 0) adjustHealth(-rand(0,2) / rating) - // Sufficient water level and nutrient level = plant healthy + // Sufficient water level and nutrient level = plant healthy but also spawns weeds else if(waterlevel > 10 && nutrilevel > 0) adjustHealth(rand(1,2) / rating) - if(prob(5)) //5 percent chance the weed population will increase + if(myseed && prob(myseed.weed_chance)) + adjustWeeds(myseed.weed_rate) + else if(prob(5)) //5 percent chance the weed population will increase adjustWeeds(1 / rating) //Toxins///////////////////////////////////////////////////////////////// @@ -316,9 +318,9 @@ if(myseed) user << "It has [myseed.plantname] planted." if (dead) - user << "It's dead." + user << "It's dead!" else if (harvest) - user << "It's ready to harvest." + user << "It's ready to harvest." else if (health <= (myseed.endurance / 2)) user << "It looks unhealthy." else @@ -328,7 +330,7 @@ user << "Water: [waterlevel]/[maxwater]" user << "Nutrient: [nutrilevel]/[maxnutri]" else - user << "It doesn't require any maintenance." + user << "It doesn't require any water or nutrients." if(weedlevel >= 5) user << "[src] is filled with weeds!" @@ -345,7 +347,7 @@ qdel(myseed) myseed = null else - oldPlantName = "Empty tray" + oldPlantName = "empty tray" switch(rand(1,18)) // randomly pick predominative weed if(16 to 18) myseed = new /obj/item/seeds/reishi(src) @@ -370,7 +372,7 @@ weedlevel = 0 // Reset pestlevel = 0 // Reset update_icon() - visible_message("[oldPlantName] overtaken by [myseed.plantname].") + visible_message("The [oldPlantName] is overtaken by some [myseed.plantname]!") /obj/machinery/hydroponics/proc/mutate(lifemut = 2, endmut = 5, productmut = 1, yieldmut = 2, potmut = 25) // Mutates the current seed @@ -404,7 +406,7 @@ sleep(5) // Wait a while update_icon() - visible_message("[oldPlantName] suddenly mutated into [myseed.plantname]!") + visible_message("[oldPlantName] suddenly mutates into [myseed.plantname]!") /obj/machinery/hydroponics/proc/mutateweed() // If the weeds gets the mutagent instead. Mind you, this pretty much destroys the old plant @@ -424,9 +426,9 @@ sleep(5) // Wait a while update_icon() - visible_message("The mutated weeds in [src] spawned a [myseed.plantname]!") + visible_message("The mutated weeds in [src] spawn some [myseed.plantname]!") else - usr << "The few weeds in [src] seem to react, but only for a moment..." + usr << "The few weeds in [src] seem to react, but only for a moment..." /obj/machinery/hydroponics/proc/plantdies() // OH NOES!!!!! I put this all in one function to make things easier @@ -440,12 +442,12 @@ /obj/machinery/hydroponics/proc/mutatepest() if(pestlevel > 5) - visible_message("The pests seem to behave oddly...") + visible_message("The pests seem to behave oddly...") for(var/i=0, i<3, i++) var/obj/effect/spider/spiderling/S = new(src.loc) S.grow_as = /mob/living/simple_animal/hostile/poison/giant_spider/hunter else - usr << "The pests seem to behave oddly, but quickly settle down..." + usr << "The pests seem to behave oddly, but quickly settle down..." /obj/machinery/hydroponics/proc/applyChemicals(datum/reagents/S) if(myseed) @@ -463,13 +465,13 @@ if(41 to 65) mutate() if(21 to 41) - usr << "The plants don't seem to react..." + usr << "The plants don't seem to react..." if(11 to 20) mutateweed() if(1 to 10) mutatepest() else - usr << "Nothing happens..." + usr << "Nothing happens..." // 2 or 1 units is enough to change the yield and other stats.// Can change the yield and other stats, but requires more than mutagen else if(S.has_reagent("mutagen", 2) || S.has_reagent("radium", 5) || S.has_reagent("uranium", 5)) @@ -667,7 +669,7 @@ if(1 to 32) mutatepest() else - usr << "Nothing happens..." + usr << "Nothing happens..." /obj/machinery/hydroponics/attackby(obj/item/O, mob/user, params) //Called when mob user "attacks" it with object O @@ -803,7 +805,7 @@ else if(istype(O, /obj/item/weapon/wrench) && unwrenchable) if(using_irrigation) - user << "Unscrew the hoses first!" + user << "Disconnect the hoses first!" return if(!anchored && !isinspace()) @@ -848,7 +850,7 @@ if(myseed) //Could be that they're just using it as a de-weeder qdel(myseed) myseed = null - weedlevel = 0 //Side-effect of cleaning up those nasty weeds + weedlevel = 0 //Has a side effect of cleaning up those nasty weeds update_icon() else @@ -929,7 +931,7 @@ return // Has no lights /obj/machinery/hydroponics/soil/attackby(obj/item/O, mob/user, params) - if(istype(O, /obj/item/weapon/shovel) && !istype(O, /obj/item/weapon/shovel/spade)) + if(istype(O, /obj/item/weapon/shovel) && !istype(O, /obj/item/weapon/shovel/spade)) //Doesn't include spades because of uprooting plants user << "You clear up [src]!" qdel(src) else diff --git a/code/modules/hydroponics/seeds.dm b/code/modules/hydroponics/seeds.dm index aa7f785b152..e85418941d0 100644 --- a/code/modules/hydroponics/seeds.dm +++ b/code/modules/hydroponics/seeds.dm @@ -37,6 +37,9 @@ var/innate_yieldmod = 1 //modifier for yield, seperate to the one in Hydro trays, as that one is SPECIFICALLY for nutriment/chems (which means it's constantly reset) //This is added onto the yield mod of the hydro tray, yield *= (parent.yieldmod+innate_yieldmod) + var/weed_rate = 1 //If the chance below passes, then this many weeds sprout during growth + var/weed_chance = 5 //Percentage chance per tray update to grow weeds + /obj/item/seeds/New(loc, nogenes = 0) ..() pixel_x = rand(-8, 8) diff --git a/code/modules/mining/fulton.dm b/code/modules/mining/fulton.dm index 09c612b4f18..08da1d69739 100644 --- a/code/modules/mining/fulton.dm +++ b/code/modules/mining/fulton.dm @@ -58,6 +58,7 @@ var/list/total_extraction_beacons = list() else if(!safe_for_living_creatures && check_for_living_mobs(A)) user << "[src] is not safe for use with living creatures, they wouldn't survive the trip back!" + return if(A.loc == user || A == user) // no extracting stuff you're holding in your hands/yourself return if(A.anchored) diff --git a/code/modules/mob/living/simple_animal/friendly/drone/_drone.dm b/code/modules/mob/living/simple_animal/friendly/drone/_drone.dm index e32df3acb9b..5f46a35cf42 100644 --- a/code/modules/mob/living/simple_animal/friendly/drone/_drone.dm +++ b/code/modules/mob/living/simple_animal/friendly/drone/_drone.dm @@ -45,6 +45,7 @@ staticOverlays = list() hud_possible = list(DIAG_STAT_HUD, DIAG_HUD, ANTAG_HUD) unique_name = TRUE + faction = list("silicon") var/staticChoice = "static" var/list/staticChoices = list("static", "blank", "letter", "animal") var/picked = FALSE //Have we picked our visual appearence (+ colour if applicable) diff --git a/code/modules/mob/living/simple_animal/friendly/drone/say.dm b/code/modules/mob/living/simple_animal/friendly/drone/say.dm index ce32409fff8..8d72c6eadf8 100644 --- a/code/modules/mob/living/simple_animal/friendly/drone/say.dm +++ b/code/modules/mob/living/simple_animal/friendly/drone/say.dm @@ -1,4 +1,3 @@ - ///////////// //DRONE SAY// ///////////// @@ -23,5 +22,10 @@ /mob/living/simple_animal/drone/proc/drone_chat(msg) - var/rendered = "DRONE CHAT: [name]: [msg]" + var/rendered = "Drone Chat: \ + [name]: \ + [msg]" alert_drones(rendered, 1) + +/mob/living/simple_animal/drone/binarycheck() + return TRUE diff --git a/code/modules/mob/living/simple_animal/hostile/megafauna/megafauna.dm b/code/modules/mob/living/simple_animal/hostile/megafauna/megafauna.dm index 2c2a48a2d61..bc6dd31cdd4 100644 --- a/code/modules/mob/living/simple_animal/hostile/megafauna/megafauna.dm +++ b/code/modules/mob/living/simple_animal/hostile/megafauna/megafauna.dm @@ -12,6 +12,7 @@ atmos_requirements = list("min_oxy" = 0, "max_oxy" = 0, "min_tox" = 0, "max_tox" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0) minbodytemp = 0 maxbodytemp = INFINITY + anchored = TRUE layer = LARGE_MOB_LAYER //Looks weird with them slipping under mineral walls and cameras and shit otherwise /mob/living/simple_animal/hostile/megafauna/death(gibbed) diff --git a/code/modules/reagents/chemistry/reagents/medicine_reagents.dm b/code/modules/reagents/chemistry/reagents/medicine_reagents.dm index 7d5fe115568..46bc5472217 100644 --- a/code/modules/reagents/chemistry/reagents/medicine_reagents.dm +++ b/code/modules/reagents/chemistry/reagents/medicine_reagents.dm @@ -1003,7 +1003,7 @@ datum/reagent/medicine/syndicate_nanites/on_mob_life(mob/living/M) ..() . = 1 -/datum/reagent/medicine/earthsblood +/datum/reagent/medicine/earthsblood //Created by ambrosia gaia plants name = "Earthsblood" id = "earthsblood" description = "Ichor from an extremely powerful plant. Great for restoring wounds, but it's a little heavy on the brain." @@ -1015,11 +1015,11 @@ datum/reagent/medicine/syndicate_nanites/on_mob_life(mob/living/M) M.adjustFireLoss(-3 * REM, 0) M.adjustOxyLoss(-15 * REM, 0) M.adjustToxLoss(-3 * REM, 0) - M.adjustBrainLoss(0.5 * REM) //This does, after all, come from ambrosia + M.adjustBrainLoss(2 * REM) //This does, after all, come from ambrosia, and the most powerful ambrosia in existence, at that! M.adjustCloneLoss(-1 * REM, 0) M.adjustStaminaLoss(-30 * REM, 0) M.jitteriness = min(max(0, M.jitteriness + 3), 30) - M.druggy = min(max(0, M.druggy + 3), 15) //See above + M.druggy = min(max(0, M.druggy + 10), 15) //See above ..() . = 1 diff --git a/config/config.txt b/config/config.txt index 5cbfcd0c5b0..d04cd64cd3d 100644 --- a/config/config.txt +++ b/config/config.txt @@ -111,6 +111,19 @@ HOSTEDBY Yournamehere ## Uncomment this to stop people connecting to your server without a registered ckey. (i.e. guest-* are all blocked from connecting) GUEST_BAN +### IPINTEL: +### This allows you to detect likely proxies by checking ips against getipintel.net +## Rating to warn at: (0.90 is good, 1 is 100% likely to be a spammer/proxy, 0.8 is 80%, etc) anything equal to or higher then this number triggers an admin warning +#IPINTEL_RATING_BAD 0.90 +## Contact email, (required to use the service, leaving blank or default disables IPINTEL) +#IPINTEL_EMAIL ch@nge.me +## How long to save good matches (ipintel rate limits to 15 per minute and 500 per day. so this shouldn't be too low, getipintel.net suggests 6 hours, time is in hours) (Your ip will get banned if you go over 500 a day too many times) +#IPINTEL_SAVE_GOOD 12 +## How long to save bad matches (these numbers can change as ips change hands, best not to save these for too long in case somebody gets a new ip used by a spammer/proxy before.) +#IPINTEL_SAVE_BAD 3 +## Domain name to query (leave commented out for the default, only needed if you pay getipintel.net for more querys) +#IPINTEL_DOMAIN check.getipintel.net + ## Uncomment to allow web client connections #ALLOW_WEBCLIENT diff --git a/html/changelogs/AutoChangeLog-pr-18636.yml b/html/changelogs/AutoChangeLog-pr-18636.yml new file mode 100644 index 00000000000..63b7219dad5 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-18636.yml @@ -0,0 +1,4 @@ +author: "coiax" +delete-after: True +changes: + - rscadd: "Drones can hear robotic talk, but cannot communicate on it. AIs and cyborgs are encouraged to share information with station repair drones." diff --git a/html/changelogs/AutoChangeLog-pr-18669.yml b/html/changelogs/AutoChangeLog-pr-18669.yml new file mode 100644 index 00000000000..c47fea8e108 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-18669.yml @@ -0,0 +1,4 @@ +author: "Joan" +delete-after: True +changes: + - rscdel: "Clock cult scripture unlock now only counts humans and silicons." diff --git a/html/changelogs/AutoChangeLog-pr-18675.yml b/html/changelogs/AutoChangeLog-pr-18675.yml new file mode 100644 index 00000000000..a397fba7d15 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-18675.yml @@ -0,0 +1,4 @@ +author: "coiax" +delete-after: True +changes: + - rscdel: "AI turrets no longer fire at drones." diff --git a/icons/mob/head.dmi b/icons/mob/head.dmi index 3d7dd56ecec..35204a7ea8f 100644 Binary files a/icons/mob/head.dmi and b/icons/mob/head.dmi differ diff --git a/icons/mob/mob.dmi b/icons/mob/mob.dmi index d0d78177369..16d3103afd9 100644 Binary files a/icons/mob/mob.dmi and b/icons/mob/mob.dmi differ diff --git a/tgstation.dme b/tgstation.dme index 94ba8781c4a..301e7c4f1bd 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -136,6 +136,7 @@ #include "code\controllers\subsystem\fastprocess.dm" #include "code\controllers\subsystem\garbage.dm" #include "code\controllers\subsystem\icon_smooth.dm" +#include "code\controllers\subsystem\ipintel.dm" #include "code\controllers\subsystem\jobs.dm" #include "code\controllers\subsystem\lighting.dm" #include "code\controllers\subsystem\machines.dm" @@ -833,6 +834,7 @@ #include "code\modules\admin\create_poll.dm" #include "code\modules\admin\create_turf.dm" #include "code\modules\admin\holder2.dm" +#include "code\modules\admin\ipintel.dm" #include "code\modules\admin\IsBanned.dm" #include "code\modules\admin\NewBan.dm" #include "code\modules\admin\player_panel.dm"