diff --git a/code/game/world.dm b/code/game/world.dm
new file mode 100644
index 0000000000..5ff45cb22a
--- /dev/null
+++ b/code/game/world.dm
@@ -0,0 +1,667 @@
+
+/*
+ The initialization of the game happens roughly like this:
+
+ 1. All global variables are initialized (including the global_init instance).
+ 2. The map is initialized, and map objects are created.
+ 3. world/New() runs, creating the process scheduler (and the old master controller) and spawning their setup.
+ 4. processScheduler/setup() runs, creating all the processes. game_controller/setup() runs, calling initialize() on all movable atoms in the world.
+ 5. The gameticker is created.
+
+*/
+var/global/datum/global_init/init = new ()
+
+/*
+ Pre-map initialization stuff should go here.
+*/
+/datum/global_init/New()
+
+ makeDatumRefLists()
+ load_configuration()
+
+ initialize_integrated_circuits_list()
+
+ qdel(src) //we're done
+
+/datum/global_init/Destroy()
+ global.init = null
+ return 2 // QDEL_HINT_IWILLGC
+
+#define RECOMMENDED_VERSION 501
+/world/New()
+ world.log << "Map Loading Complete"
+ //logs
+ log_path += time2text(world.realtime, "YYYY/MM-Month/DD-Day/round-hh-mm-ss")
+ diary = file("[log_path].log")
+ href_logfile = file("[log_path]-hrefs.htm")
+ error_log = file("[log_path]-error.log")
+ debug_log = file("[log_path]-debug.log")
+ debug_log << "[log_end]\n[log_end]\nStarting up. [time_stamp()][log_end]\n---------------------[log_end]"
+ changelog_hash = md5('html/changelog.html') //used for telling if the changelog has changed recently
+
+ if(byond_version < RECOMMENDED_VERSION)
+ world.log << "Your server's byond version does not meet the recommended requirements for this server. Please update BYOND"
+
+ config.post_load()
+
+ if(config && config.server_name != null && config.server_suffix && world.port > 0)
+ // dumb and hardcoded but I don't care~
+ config.server_name += " #[(world.port % 1000) / 100]"
+
+ if(config && config.log_runtime)
+ log = file("data/logs/runtime/[time2text(world.realtime,"YYYY-MM-DD-(hh-mm-ss)")]-runtime.log")
+
+ GLOB.timezoneOffset = text2num(time2text(0,"hh")) * 36000
+
+ callHook("startup")
+ //Emergency Fix
+ load_mods()
+ //end-emergency fix
+
+ src.update_status()
+
+ . = ..()
+
+#if UNIT_TEST
+ log_unit_test("Unit Tests Enabled. This will destroy the world when testing is complete.")
+ log_unit_test("If you did not intend to enable this please check code/__defines/unit_testing.dm")
+#endif
+
+ // Set up roundstart seed list.
+ plant_controller = new()
+
+ // This is kinda important. Set up details of what the hell things are made of.
+ populate_material_list()
+
+ // Create frame types.
+ populate_frame_types()
+
+ // Create floor types.
+ populate_flooring_types()
+
+ // Create robolimbs for chargen.
+ populate_robolimb_list()
+
+ processScheduler = new
+ master_controller = new /datum/controller/game_controller()
+
+ processScheduler.deferSetupFor(/datum/controller/process/ticker)
+ processScheduler.setup()
+ Master.Initialize(10, FALSE)
+
+ spawn(1)
+ master_controller.setup()
+#if UNIT_TEST
+ initialize_unit_tests()
+#endif
+
+ spawn(3000) //so we aren't adding to the round-start lag
+ if(config.ToRban)
+ ToRban_autoupdate()
+
+#undef RECOMMENDED_VERSION
+
+ return
+
+var/world_topic_spam_protect_ip = "0.0.0.0"
+var/world_topic_spam_protect_time = world.timeofday
+
+/world/Topic(T, addr, master, key)
+ debug_log << "TOPIC: \"[T]\", from:[addr], master:[master], key:[key][log_end]"
+
+ if (T == "ping")
+ var/x = 1
+ for (var/client/C)
+ x++
+ return x
+
+ else if(T == "players")
+ var/n = 0
+ for(var/mob/M in player_list)
+ if(M.client)
+ n++
+ return n
+
+ else if (copytext(T,1,7) == "status")
+ var/input[] = params2list(T)
+ var/list/s = list()
+ s["version"] = game_version
+ s["mode"] = master_mode
+ s["respawn"] = config.abandon_allowed
+ s["enter"] = config.enter_allowed
+ s["vote"] = config.allow_vote_mode
+ s["ai"] = config.allow_ai
+ s["host"] = host ? host : null
+
+ // This is dumb, but spacestation13.com's banners break if player count isn't the 8th field of the reply, so... this has to go here.
+ s["players"] = 0
+ s["stationtime"] = stationtime2text()
+ s["roundduration"] = roundduration2text()
+
+ if(input["status"] == "2")
+ var/list/players = list()
+ var/list/admins = list()
+
+ for(var/client/C in GLOB.clients)
+ if(C.holder)
+ if(C.holder.fakekey)
+ continue
+ admins[C.key] = C.holder.rank
+ players += C.key
+
+ s["players"] = players.len
+ s["playerlist"] = list2params(players)
+ var/list/adm = get_admin_counts()
+ var/list/presentmins = adm["present"]
+ var/list/afkmins = adm["afk"]
+ s["admins"] = presentmins.len + afkmins.len //equivalent to the info gotten from adminwho
+ s["adminlist"] = list2params(admins)
+ else
+ var/n = 0
+ var/admins = 0
+
+ for(var/client/C in GLOB.clients)
+ if(C.holder)
+ if(C.holder.fakekey)
+ continue //so stealthmins aren't revealed by the hub
+ admins++
+ s["player[n]"] = C.key
+ n++
+
+ s["players"] = n
+ s["admins"] = admins
+
+ return list2params(s)
+
+ else if(T == "manifest")
+ var/list/positions = list()
+ var/list/set_names = list(
+ "heads" = command_positions,
+ "sec" = security_positions,
+ "eng" = engineering_positions,
+ "med" = medical_positions,
+ "sci" = science_positions,
+ "car" = cargo_positions,
+ "civ" = civilian_positions,
+ "bot" = nonhuman_positions
+ )
+
+ for(var/datum/data/record/t in data_core.general)
+ var/name = t.fields["name"]
+ var/rank = t.fields["rank"]
+ var/real_rank = make_list_rank(t.fields["real_rank"])
+
+ var/department = 0
+ for(var/k in set_names)
+ if(real_rank in set_names[k])
+ if(!positions[k])
+ positions[k] = list()
+ positions[k][name] = rank
+ department = 1
+ if(!department)
+ if(!positions["misc"])
+ positions["misc"] = list()
+ positions["misc"][name] = rank
+
+ // Synthetics don't have actual records, so we will pull them from here.
+ for(var/mob/living/silicon/ai/ai in mob_list)
+ if(!positions["bot"])
+ positions["bot"] = list()
+ positions["bot"][ai.name] = "Artificial Intelligence"
+ for(var/mob/living/silicon/robot/robot in mob_list)
+ // No combat/syndicate cyborgs, no drones.
+ if(robot.module && robot.module.hide_on_manifest)
+ continue
+ if(!positions["bot"])
+ positions["bot"] = list()
+ positions["bot"][robot.name] = "[robot.modtype] [robot.braintype]"
+
+ for(var/k in positions)
+ positions[k] = list2params(positions[k]) // converts positions["heads"] = list("Bob"="Captain", "Bill"="CMO") into positions["heads"] = "Bob=Captain&Bill=CMO"
+
+ return list2params(positions)
+
+ else if(T == "revision")
+ if(revdata.revision)
+ return list2params(list(branch = revdata.branch, date = revdata.date, revision = revdata.revision))
+ else
+ return "unknown"
+
+ else if(copytext(T,1,5) == "info")
+ var/input[] = params2list(T)
+ if(input["key"] != config.comms_password)
+ if(world_topic_spam_protect_ip == addr && abs(world_topic_spam_protect_time - world.time) < 50)
+
+ spawn(50)
+ world_topic_spam_protect_time = world.time
+ return "Bad Key (Throttled)"
+
+ world_topic_spam_protect_time = world.time
+ world_topic_spam_protect_ip = addr
+
+ return "Bad Key"
+
+ var/list/search = params2list(input["info"])
+ var/list/ckeysearch = list()
+ for(var/text in search)
+ ckeysearch += ckey(text)
+
+ var/list/match = list()
+
+ for(var/mob/M in mob_list)
+ var/strings = list(M.name, M.ckey)
+ if(M.mind)
+ strings += M.mind.assigned_role
+ strings += M.mind.special_role
+ for(var/text in strings)
+ if(ckey(text) in ckeysearch)
+ match[M] += 10 // an exact match is far better than a partial one
+ else
+ for(var/searchstr in search)
+ if(findtext(text, searchstr))
+ match[M] += 1
+
+ var/maxstrength = 0
+ for(var/mob/M in match)
+ maxstrength = max(match[M], maxstrength)
+ for(var/mob/M in match)
+ if(match[M] < maxstrength)
+ match -= M
+
+ if(!match.len)
+ return "No matches"
+ else if(match.len == 1)
+ var/mob/M = match[1]
+ var/info = list()
+ info["key"] = M.key
+ info["name"] = M.name == M.real_name ? M.name : "[M.name] ([M.real_name])"
+ info["role"] = M.mind ? (M.mind.assigned_role ? M.mind.assigned_role : "No role") : "No mind"
+ var/turf/MT = get_turf(M)
+ info["loc"] = M.loc ? "[M.loc]" : "null"
+ info["turf"] = MT ? "[MT] @ [MT.x], [MT.y], [MT.z]" : "null"
+ info["area"] = MT ? "[MT.loc]" : "null"
+ info["antag"] = M.mind ? (M.mind.special_role ? M.mind.special_role : "Not antag") : "No mind"
+ info["hasbeenrev"] = M.mind ? M.mind.has_been_rev : "No mind"
+ info["stat"] = M.stat
+ info["type"] = M.type
+ if(isliving(M))
+ var/mob/living/L = M
+ info["damage"] = list2params(list(
+ oxy = L.getOxyLoss(),
+ tox = L.getToxLoss(),
+ fire = L.getFireLoss(),
+ brute = L.getBruteLoss(),
+ clone = L.getCloneLoss(),
+ brain = L.getBrainLoss()
+ ))
+ else
+ info["damage"] = "non-living"
+ info["gender"] = M.gender
+ return list2params(info)
+ else
+ var/list/ret = list()
+ for(var/mob/M in match)
+ ret[M.key] = M.name
+ return list2params(ret)
+
+ else if(copytext(T,1,9) == "adminmsg")
+ /*
+ We got an adminmsg from IRC bot lets split the input then validate the input.
+ expected output:
+ 1. adminmsg = ckey of person the message is to
+ 2. msg = contents of message, parems2list requires
+ 3. validatationkey = the key the bot has, it should match the gameservers commspassword in it's configuration.
+ 4. sender = the ircnick that send the message.
+ */
+
+
+ var/input[] = params2list(T)
+ if(input["key"] != config.comms_password)
+ if(world_topic_spam_protect_ip == addr && abs(world_topic_spam_protect_time - world.time) < 50)
+
+ spawn(50)
+ world_topic_spam_protect_time = world.time
+ return "Bad Key (Throttled)"
+
+ world_topic_spam_protect_time = world.time
+ world_topic_spam_protect_ip = addr
+
+ return "Bad Key"
+
+ var/client/C
+ var/req_ckey = ckey(input["adminmsg"])
+
+ for(var/client/K in GLOB.clients)
+ if(K.ckey == req_ckey)
+ C = K
+ break
+ if(!C)
+ return "No client with that name on server"
+
+ var/rank = input["rank"]
+ if(!rank)
+ rank = "Admin"
+
+ var/message = "IRC-[rank] PM from IRC-[input["sender"]]: [input["msg"]]"
+ var/amessage = "IRC-[rank] PM from IRC-[input["sender"]] to [key_name(C)] : [input["msg"]]"
+
+ C.received_irc_pm = world.time
+ C.irc_admin = input["sender"]
+
+ C << 'sound/effects/adminhelp.ogg'
+ C << message
+
+
+ for(var/client/A in admins)
+ if(A != C)
+ A << amessage
+
+ return "Message Successful"
+
+ else if(copytext(T,1,6) == "notes")
+ /*
+ We got a request for notes from the IRC Bot
+ expected output:
+ 1. notes = ckey of person the notes lookup is for
+ 2. validationkey = the key the bot has, it should match the gameservers commspassword in it's configuration.
+ */
+ var/input[] = params2list(T)
+ if(input["key"] != config.comms_password)
+ if(world_topic_spam_protect_ip == addr && abs(world_topic_spam_protect_time - world.time) < 50)
+
+ spawn(50)
+ world_topic_spam_protect_time = world.time
+ return "Bad Key (Throttled)"
+
+ world_topic_spam_protect_time = world.time
+ world_topic_spam_protect_ip = addr
+ return "Bad Key"
+
+ return show_player_info_irc(ckey(input["notes"]))
+
+ else if(copytext(T,1,4) == "age")
+ var/input[] = params2list(T)
+ if(input["key"] != config.comms_password)
+ if(world_topic_spam_protect_ip == addr && abs(world_topic_spam_protect_time - world.time) < 50)
+ spawn(50)
+ world_topic_spam_protect_time = world.time
+ return "Bad Key (Throttled)"
+
+ world_topic_spam_protect_time = world.time
+ world_topic_spam_protect_ip = addr
+ return "Bad Key"
+
+ var/age = get_player_age(input["age"])
+ if(isnum(age))
+ if(age >= 0)
+ return "[age]"
+ else
+ return "Ckey not found"
+ else
+ return "Database connection failed or not set up"
+
+
+/world/Reboot(reason = 0, fast_track = FALSE)
+ /*spawn(0)
+ world << sound(pick('sound/AI/newroundsexy.ogg','sound/misc/apcdestroyed.ogg','sound/misc/bangindonk.ogg')) // random end sounds!! - LastyBatsy
+ */
+ if (reason || fast_track) //special reboot, do none of the normal stuff
+ if (usr)
+ log_admin("[key_name(usr)] Has requested an immediate world restart via client side debugging tools")
+ message_admins("[key_name_admin(usr)] Has requested an immediate world restart via client side debugging tools")
+ world << "[key_name_admin(usr)] has requested an immediate world restart via client side debugging tools"
+
+ else
+ world << "Rebooting world immediately due to host request"
+ else
+ processScheduler.stop()
+ Master.Shutdown() //run SS shutdowns
+ for(var/client/C in GLOB.clients)
+ if(config.server) //if you set a server location in config.txt, it sends you there instead of trying to reconnect to the same world address. -- NeoFite
+ C << link("byond://[config.server]")
+
+ log_world("World rebooted at [time_stamp()]")
+ ..()
+
+/hook/startup/proc/loadMode()
+ world.load_mode()
+ return 1
+
+/world/proc/load_mode()
+ if(!fexists("data/mode.txt"))
+ return
+
+
+ var/list/Lines = file2list("data/mode.txt")
+ if(Lines.len)
+ if(Lines[1])
+ master_mode = Lines[1]
+ log_misc("Saved mode is '[master_mode]'")
+
+/world/proc/save_mode(var/the_mode)
+ var/F = file("data/mode.txt")
+ fdel(F)
+ F << the_mode
+
+
+/hook/startup/proc/loadMOTD()
+ world.load_motd()
+ return 1
+
+/world/proc/load_motd()
+ join_motd = file2text("config/motd.txt")
+
+
+/proc/load_configuration()
+ config = new /datum/configuration()
+ config.load("config/config.txt")
+ config.load("config/game_options.txt","game_options")
+ config.loadsql("config/dbconfig.txt")
+ config.loadforumsql("config/forumdbconfig.txt")
+
+/hook/startup/proc/loadMods()
+ world.load_mods()
+ world.load_mentors() // no need to write another hook.
+ return 1
+
+/world/proc/load_mods()
+ if(config.admin_legacy_system)
+ var/text = file2text("config/moderators.txt")
+ if (!text)
+ error("Failed to load config/mods.txt")
+ else
+ var/list/lines = splittext(text, "\n")
+ for(var/line in lines)
+ if (!line)
+ continue
+
+ if (copytext(line, 1, 2) == ";")
+ continue
+
+ var/title = "Moderator"
+ var/rights = admin_ranks[title]
+
+ var/ckey = copytext(line, 1, length(line)+1)
+ var/datum/admins/D = new /datum/admins(title, rights, ckey)
+ D.associate(GLOB.directory[ckey])
+
+/world/proc/load_mentors()
+ if(config.admin_legacy_system)
+ var/text = file2text("config/mentors.txt")
+ if (!text)
+ error("Failed to load config/mentors.txt")
+ else
+ var/list/lines = splittext(text, "\n")
+ for(var/line in lines)
+ if (!line)
+ continue
+ if (copytext(line, 1, 2) == ";")
+ continue
+
+ var/title = "Mentor"
+ var/rights = admin_ranks[title]
+
+ var/ckey = copytext(line, 1, length(line)+1)
+ var/datum/admins/D = new /datum/admins(title, rights, ckey)
+ D.associate(GLOB.directory[ckey])
+
+/world/proc/update_status()
+ var/s = ""
+
+ if (config && config.server_name)
+ s += "[config.server_name] — "
+
+ s += "[station_name()]";
+ s += " ("
+ s += "" //Change this to wherever you want the hub to link to.
+// s += "[game_version]"
+ s += "Default" //Replace this with something else. Or ever better, delete it and uncomment the game version.
+ s += ""
+ s += ")"
+
+ var/list/features = list()
+
+ if(ticker)
+ if(master_mode)
+ features += master_mode
+ else
+ features += "STARTING"
+
+ if (!config.enter_allowed)
+ features += "closed"
+
+ features += config.abandon_allowed ? "respawn" : "no respawn"
+
+ if (config && config.allow_vote_mode)
+ features += "vote"
+
+ if (config && config.allow_ai)
+ features += "AI allowed"
+
+ var/n = 0
+ for (var/mob/M in player_list)
+ if (M.client)
+ n++
+
+ if (n > 1)
+ features += "~[n] players"
+ else if (n > 0)
+ features += "~[n] player"
+
+
+ if (config && config.hostedby)
+ features += "hosted by [config.hostedby]"
+
+ if (features)
+ s += ": [jointext(features, ", ")]"
+
+ /* does this help? I do not know */
+ if (src.status != s)
+ src.status = s
+
+#define FAILED_DB_CONNECTION_CUTOFF 5
+var/failed_db_connections = 0
+var/failed_old_db_connections = 0
+
+/hook/startup/proc/connectDB()
+ if(!config.sql_enabled)
+ world.log << "SQL connection disabled in config."
+ else if(!setup_database_connection())
+ world.log << "Your server failed to establish a connection with the feedback database."
+ else
+ world.log << "Feedback database connection established."
+ return 1
+
+proc/setup_database_connection()
+
+ if(failed_db_connections > FAILED_DB_CONNECTION_CUTOFF) //If it failed to establish a connection more than 5 times in a row, don't bother attempting to conenct anymore.
+ return 0
+
+ if(!dbcon)
+ dbcon = new()
+
+ var/user = sqlfdbklogin
+ var/pass = sqlfdbkpass
+ var/db = sqlfdbkdb
+ var/address = sqladdress
+ var/port = sqlport
+
+ dbcon.Connect("dbi:mysql:[db]:[address]:[port]","[user]","[pass]")
+ . = dbcon.IsConnected()
+ if ( . )
+ failed_db_connections = 0 //If this connection succeeded, reset the failed connections counter.
+ else
+ failed_db_connections++ //If it failed, increase the failed connections counter.
+ world.log << dbcon.ErrorMsg()
+
+ return .
+
+//This proc ensures that the connection to the feedback database (global variable dbcon) is established
+proc/establish_db_connection()
+ if(failed_db_connections > FAILED_DB_CONNECTION_CUTOFF)
+ return 0
+
+ if(!dbcon || !dbcon.IsConnected())
+ return setup_database_connection()
+ else
+ return 1
+
+
+/hook/startup/proc/connectOldDB()
+ if(!config.sql_enabled)
+ world.log << "SQL connection disabled in config."
+ else if(!setup_old_database_connection())
+ world.log << "Your server failed to establish a connection with the SQL database."
+ else
+ world.log << "SQL database connection established."
+ return 1
+
+//These two procs are for the old database, while it's being phased out. See the tgstation.sql file in the SQL folder for more information.
+proc/setup_old_database_connection()
+
+ if(failed_old_db_connections > FAILED_DB_CONNECTION_CUTOFF) //If it failed to establish a connection more than 5 times in a row, don't bother attempting to conenct anymore.
+ return 0
+
+ if(!dbcon_old)
+ dbcon_old = new()
+
+ var/user = sqllogin
+ var/pass = sqlpass
+ var/db = sqldb
+ var/address = sqladdress
+ var/port = sqlport
+
+ dbcon_old.Connect("dbi:mysql:[db]:[address]:[port]","[user]","[pass]")
+ . = dbcon_old.IsConnected()
+ if ( . )
+ failed_old_db_connections = 0 //If this connection succeeded, reset the failed connections counter.
+ else
+ failed_old_db_connections++ //If it failed, increase the failed connections counter.
+ world.log << dbcon.ErrorMsg()
+
+ return .
+
+//This proc ensures that the connection to the feedback database (global variable dbcon) is established
+proc/establish_old_db_connection()
+ if(failed_old_db_connections > FAILED_DB_CONNECTION_CUTOFF)
+ return 0
+
+ if(!dbcon_old || !dbcon_old.IsConnected())
+ return setup_old_database_connection()
+ else
+ return 1
+
+// Things to do when a new z-level was just made.
+/world/proc/max_z_changed()
+ if(!islist(GLOB.players_by_zlevel))
+ GLOB.players_by_zlevel = new /list(world.maxz, 0)
+ while(GLOB.players_by_zlevel.len < world.maxz)
+ GLOB.players_by_zlevel.len++
+ GLOB.players_by_zlevel[GLOB.players_by_zlevel.len] = list()
+
+// Call this to make a new blank z-level, don't modify maxz directly.
+/world/proc/increment_max_z()
+ maxz++
+ max_z_changed()
+
+#undef FAILED_DB_CONNECTION_CUTOFF
diff --git a/code/world.dm b/code/world.dm
index 0fb016581c..e25c4c82ff 100644
--- a/code/world.dm
+++ b/code/world.dm
@@ -1,678 +1,6 @@
-
-/*
- The initialization of the game happens roughly like this:
-
- 1. All global variables are initialized (including the global_init instance).
- 2. The map is initialized, and map objects are created.
- 3. world/New() runs, creating the process scheduler (and the old master controller) and spawning their setup.
- 4. processScheduler/setup() runs, creating all the processes. game_controller/setup() runs, calling initialize() on all movable atoms in the world.
- 5. The gameticker is created.
-
-*/
-var/global/datum/global_init/init = new ()
-
-/*
- Pre-map initialization stuff should go here.
-*/
-/datum/global_init/New()
-
- makeDatumRefLists()
- load_configuration()
-
- initialize_chemical_reagents()
- initialize_chemical_reactions()
- initialize_integrated_circuits_list()
-
- qdel(src) //we're done
-
-/datum/global_init/Destroy()
- global.init = null
- return 2 // QDEL_HINT_IWILLGC
-
/world
mob = /mob/new_player
turf = /turf/space
area = /area/space
view = "15x15"
cache_lifespan = 7
-
-
-
-#define RECOMMENDED_VERSION 501
-/world/New()
- world.log << "Map Loading Complete"
- //logs
- log_path += time2text(world.realtime, "YYYY/MM-Month/DD-Day/round-hh-mm-ss")
- diary = file("[log_path].log")
- href_logfile = file("[log_path]-hrefs.htm")
- error_log = file("[log_path]-error.log")
- debug_log = file("[log_path]-debug.log")
- debug_log << "[log_end]\n[log_end]\nStarting up. [time_stamp()][log_end]\n---------------------[log_end]"
- changelog_hash = md5('html/changelog.html') //used for telling if the changelog has changed recently
-
- if(byond_version < RECOMMENDED_VERSION)
- world.log << "Your server's byond version does not meet the recommended requirements for this server. Please update BYOND"
-
- config.post_load()
-
- if(config && config.server_name != null && config.server_suffix && world.port > 0)
- // dumb and hardcoded but I don't care~
- config.server_name += " #[(world.port % 1000) / 100]"
-
- if(config && config.log_runtime)
- log = file("data/logs/runtime/[time2text(world.realtime,"YYYY-MM-DD-(hh-mm-ss)")]-runtime.log")
-
- GLOB.timezoneOffset = text2num(time2text(0,"hh")) * 36000
-
- callHook("startup")
- //Emergency Fix
- load_mods()
- //end-emergency fix
-
- src.update_status()
-
- . = ..()
-
-#if UNIT_TEST
- log_unit_test("Unit Tests Enabled. This will destroy the world when testing is complete.")
- log_unit_test("If you did not intend to enable this please check code/__defines/unit_testing.dm")
-#endif
-
- // Set up roundstart seed list.
- plant_controller = new()
-
- // This is kinda important. Set up details of what the hell things are made of.
- populate_material_list()
-
- // Create frame types.
- populate_frame_types()
-
- // Create floor types.
- populate_flooring_types()
-
- // Create robolimbs for chargen.
- populate_robolimb_list()
-
- processScheduler = new
- master_controller = new /datum/controller/game_controller()
-
- processScheduler.deferSetupFor(/datum/controller/process/ticker)
- processScheduler.setup()
- Master.Initialize(10, FALSE)
-
- spawn(1)
- master_controller.setup()
-#if UNIT_TEST
- initialize_unit_tests()
-#endif
-
- spawn(3000) //so we aren't adding to the round-start lag
- if(config.ToRban)
- ToRban_autoupdate()
-
-#undef RECOMMENDED_VERSION
-
- return
-
-var/world_topic_spam_protect_ip = "0.0.0.0"
-var/world_topic_spam_protect_time = world.timeofday
-
-/world/Topic(T, addr, master, key)
- debug_log << "TOPIC: \"[T]\", from:[addr], master:[master], key:[key][log_end]"
-
- if (T == "ping")
- var/x = 1
- for (var/client/C)
- x++
- return x
-
- else if(T == "players")
- var/n = 0
- for(var/mob/M in player_list)
- if(M.client)
- n++
- return n
-
- else if (copytext(T,1,7) == "status")
- var/input[] = params2list(T)
- var/list/s = list()
- s["version"] = game_version
- s["mode"] = master_mode
- s["respawn"] = config.abandon_allowed
- s["enter"] = config.enter_allowed
- s["vote"] = config.allow_vote_mode
- s["ai"] = config.allow_ai
- s["host"] = host ? host : null
-
- // This is dumb, but spacestation13.com's banners break if player count isn't the 8th field of the reply, so... this has to go here.
- s["players"] = 0
- s["stationtime"] = stationtime2text()
- s["roundduration"] = roundduration2text()
-
- if(input["status"] == "2")
- var/list/players = list()
- var/list/admins = list()
-
- for(var/client/C in GLOB.clients)
- if(C.holder)
- if(C.holder.fakekey)
- continue
- admins[C.key] = C.holder.rank
- players += C.key
-
- s["players"] = players.len
- s["playerlist"] = list2params(players)
- var/list/adm = get_admin_counts()
- var/list/presentmins = adm["present"]
- var/list/afkmins = adm["afk"]
- s["admins"] = presentmins.len + afkmins.len //equivalent to the info gotten from adminwho
- s["adminlist"] = list2params(admins)
- else
- var/n = 0
- var/admins = 0
-
- for(var/client/C in GLOB.clients)
- if(C.holder)
- if(C.holder.fakekey)
- continue //so stealthmins aren't revealed by the hub
- admins++
- s["player[n]"] = C.key
- n++
-
- s["players"] = n
- s["admins"] = admins
-
- return list2params(s)
-
- else if(T == "manifest")
- var/list/positions = list()
- var/list/set_names = list(
- "heads" = command_positions,
- "sec" = security_positions,
- "eng" = engineering_positions,
- "med" = medical_positions,
- "sci" = science_positions,
- "car" = cargo_positions,
- "civ" = civilian_positions,
- "bot" = nonhuman_positions
- )
-
- for(var/datum/data/record/t in data_core.general)
- var/name = t.fields["name"]
- var/rank = t.fields["rank"]
- var/real_rank = make_list_rank(t.fields["real_rank"])
-
- var/department = 0
- for(var/k in set_names)
- if(real_rank in set_names[k])
- if(!positions[k])
- positions[k] = list()
- positions[k][name] = rank
- department = 1
- if(!department)
- if(!positions["misc"])
- positions["misc"] = list()
- positions["misc"][name] = rank
-
- // Synthetics don't have actual records, so we will pull them from here.
- for(var/mob/living/silicon/ai/ai in mob_list)
- if(!positions["bot"])
- positions["bot"] = list()
- positions["bot"][ai.name] = "Artificial Intelligence"
- for(var/mob/living/silicon/robot/robot in mob_list)
- // No combat/syndicate cyborgs, no drones.
- if(robot.module && robot.module.hide_on_manifest)
- continue
- if(!positions["bot"])
- positions["bot"] = list()
- positions["bot"][robot.name] = "[robot.modtype] [robot.braintype]"
-
- for(var/k in positions)
- positions[k] = list2params(positions[k]) // converts positions["heads"] = list("Bob"="Captain", "Bill"="CMO") into positions["heads"] = "Bob=Captain&Bill=CMO"
-
- return list2params(positions)
-
- else if(T == "revision")
- if(revdata.revision)
- return list2params(list(branch = revdata.branch, date = revdata.date, revision = revdata.revision))
- else
- return "unknown"
-
- else if(copytext(T,1,5) == "info")
- var/input[] = params2list(T)
- if(input["key"] != config.comms_password)
- if(world_topic_spam_protect_ip == addr && abs(world_topic_spam_protect_time - world.time) < 50)
-
- spawn(50)
- world_topic_spam_protect_time = world.time
- return "Bad Key (Throttled)"
-
- world_topic_spam_protect_time = world.time
- world_topic_spam_protect_ip = addr
-
- return "Bad Key"
-
- var/list/search = params2list(input["info"])
- var/list/ckeysearch = list()
- for(var/text in search)
- ckeysearch += ckey(text)
-
- var/list/match = list()
-
- for(var/mob/M in mob_list)
- var/strings = list(M.name, M.ckey)
- if(M.mind)
- strings += M.mind.assigned_role
- strings += M.mind.special_role
- for(var/text in strings)
- if(ckey(text) in ckeysearch)
- match[M] += 10 // an exact match is far better than a partial one
- else
- for(var/searchstr in search)
- if(findtext(text, searchstr))
- match[M] += 1
-
- var/maxstrength = 0
- for(var/mob/M in match)
- maxstrength = max(match[M], maxstrength)
- for(var/mob/M in match)
- if(match[M] < maxstrength)
- match -= M
-
- if(!match.len)
- return "No matches"
- else if(match.len == 1)
- var/mob/M = match[1]
- var/info = list()
- info["key"] = M.key
- info["name"] = M.name == M.real_name ? M.name : "[M.name] ([M.real_name])"
- info["role"] = M.mind ? (M.mind.assigned_role ? M.mind.assigned_role : "No role") : "No mind"
- var/turf/MT = get_turf(M)
- info["loc"] = M.loc ? "[M.loc]" : "null"
- info["turf"] = MT ? "[MT] @ [MT.x], [MT.y], [MT.z]" : "null"
- info["area"] = MT ? "[MT.loc]" : "null"
- info["antag"] = M.mind ? (M.mind.special_role ? M.mind.special_role : "Not antag") : "No mind"
- info["hasbeenrev"] = M.mind ? M.mind.has_been_rev : "No mind"
- info["stat"] = M.stat
- info["type"] = M.type
- if(isliving(M))
- var/mob/living/L = M
- info["damage"] = list2params(list(
- oxy = L.getOxyLoss(),
- tox = L.getToxLoss(),
- fire = L.getFireLoss(),
- brute = L.getBruteLoss(),
- clone = L.getCloneLoss(),
- brain = L.getBrainLoss()
- ))
- else
- info["damage"] = "non-living"
- info["gender"] = M.gender
- return list2params(info)
- else
- var/list/ret = list()
- for(var/mob/M in match)
- ret[M.key] = M.name
- return list2params(ret)
-
- else if(copytext(T,1,9) == "adminmsg")
- /*
- We got an adminmsg from IRC bot lets split the input then validate the input.
- expected output:
- 1. adminmsg = ckey of person the message is to
- 2. msg = contents of message, parems2list requires
- 3. validatationkey = the key the bot has, it should match the gameservers commspassword in it's configuration.
- 4. sender = the ircnick that send the message.
- */
-
-
- var/input[] = params2list(T)
- if(input["key"] != config.comms_password)
- if(world_topic_spam_protect_ip == addr && abs(world_topic_spam_protect_time - world.time) < 50)
-
- spawn(50)
- world_topic_spam_protect_time = world.time
- return "Bad Key (Throttled)"
-
- world_topic_spam_protect_time = world.time
- world_topic_spam_protect_ip = addr
-
- return "Bad Key"
-
- var/client/C
- var/req_ckey = ckey(input["adminmsg"])
-
- for(var/client/K in GLOB.clients)
- if(K.ckey == req_ckey)
- C = K
- break
- if(!C)
- return "No client with that name on server"
-
- var/rank = input["rank"]
- if(!rank)
- rank = "Admin"
-
- var/message = "IRC-[rank] PM from IRC-[input["sender"]]: [input["msg"]]"
- var/amessage = "IRC-[rank] PM from IRC-[input["sender"]] to [key_name(C)] : [input["msg"]]"
-
- C.received_irc_pm = world.time
- C.irc_admin = input["sender"]
-
- C << 'sound/effects/adminhelp.ogg'
- C << message
-
-
- for(var/client/A in admins)
- if(A != C)
- A << amessage
-
- return "Message Successful"
-
- else if(copytext(T,1,6) == "notes")
- /*
- We got a request for notes from the IRC Bot
- expected output:
- 1. notes = ckey of person the notes lookup is for
- 2. validationkey = the key the bot has, it should match the gameservers commspassword in it's configuration.
- */
- var/input[] = params2list(T)
- if(input["key"] != config.comms_password)
- if(world_topic_spam_protect_ip == addr && abs(world_topic_spam_protect_time - world.time) < 50)
-
- spawn(50)
- world_topic_spam_protect_time = world.time
- return "Bad Key (Throttled)"
-
- world_topic_spam_protect_time = world.time
- world_topic_spam_protect_ip = addr
- return "Bad Key"
-
- return show_player_info_irc(ckey(input["notes"]))
-
- else if(copytext(T,1,4) == "age")
- var/input[] = params2list(T)
- if(input["key"] != config.comms_password)
- if(world_topic_spam_protect_ip == addr && abs(world_topic_spam_protect_time - world.time) < 50)
- spawn(50)
- world_topic_spam_protect_time = world.time
- return "Bad Key (Throttled)"
-
- world_topic_spam_protect_time = world.time
- world_topic_spam_protect_ip = addr
- return "Bad Key"
-
- var/age = get_player_age(input["age"])
- if(isnum(age))
- if(age >= 0)
- return "[age]"
- else
- return "Ckey not found"
- else
- return "Database connection failed or not set up"
-
-
-/world/Reboot(reason = 0, fast_track = FALSE)
- /*spawn(0)
- world << sound(pick('sound/AI/newroundsexy.ogg','sound/misc/apcdestroyed.ogg','sound/misc/bangindonk.ogg')) // random end sounds!! - LastyBatsy
- */
- if (reason || fast_track) //special reboot, do none of the normal stuff
- if (usr)
- log_admin("[key_name(usr)] Has requested an immediate world restart via client side debugging tools")
- message_admins("[key_name_admin(usr)] Has requested an immediate world restart via client side debugging tools")
- world << "[key_name_admin(usr)] has requested an immediate world restart via client side debugging tools"
-
- else
- world << "Rebooting world immediately due to host request"
- else
- processScheduler.stop()
- Master.Shutdown() //run SS shutdowns
- for(var/client/C in GLOB.clients)
- if(config.server) //if you set a server location in config.txt, it sends you there instead of trying to reconnect to the same world address. -- NeoFite
- C << link("byond://[config.server]")
-
- log_world("World rebooted at [time_stamp()]")
- ..()
-
-/hook/startup/proc/loadMode()
- world.load_mode()
- return 1
-
-/world/proc/load_mode()
- if(!fexists("data/mode.txt"))
- return
-
-
- var/list/Lines = file2list("data/mode.txt")
- if(Lines.len)
- if(Lines[1])
- master_mode = Lines[1]
- log_misc("Saved mode is '[master_mode]'")
-
-/world/proc/save_mode(var/the_mode)
- var/F = file("data/mode.txt")
- fdel(F)
- F << the_mode
-
-
-/hook/startup/proc/loadMOTD()
- world.load_motd()
- return 1
-
-/world/proc/load_motd()
- join_motd = file2text("config/motd.txt")
-
-
-/proc/load_configuration()
- config = new /datum/configuration()
- config.load("config/config.txt")
- config.load("config/game_options.txt","game_options")
- config.loadsql("config/dbconfig.txt")
- config.loadforumsql("config/forumdbconfig.txt")
-
-/hook/startup/proc/loadMods()
- world.load_mods()
- world.load_mentors() // no need to write another hook.
- return 1
-
-/world/proc/load_mods()
- if(config.admin_legacy_system)
- var/text = file2text("config/moderators.txt")
- if (!text)
- error("Failed to load config/mods.txt")
- else
- var/list/lines = splittext(text, "\n")
- for(var/line in lines)
- if (!line)
- continue
-
- if (copytext(line, 1, 2) == ";")
- continue
-
- var/title = "Moderator"
- var/rights = admin_ranks[title]
-
- var/ckey = copytext(line, 1, length(line)+1)
- var/datum/admins/D = new /datum/admins(title, rights, ckey)
- D.associate(GLOB.directory[ckey])
-
-/world/proc/load_mentors()
- if(config.admin_legacy_system)
- var/text = file2text("config/mentors.txt")
- if (!text)
- error("Failed to load config/mentors.txt")
- else
- var/list/lines = splittext(text, "\n")
- for(var/line in lines)
- if (!line)
- continue
- if (copytext(line, 1, 2) == ";")
- continue
-
- var/title = "Mentor"
- var/rights = admin_ranks[title]
-
- var/ckey = copytext(line, 1, length(line)+1)
- var/datum/admins/D = new /datum/admins(title, rights, ckey)
- D.associate(GLOB.directory[ckey])
-
-/world/proc/update_status()
- var/s = ""
-
- if (config && config.server_name)
- s += "[config.server_name] — "
-
- s += "[station_name()]";
- s += " ("
- s += "" //Change this to wherever you want the hub to link to.
-// s += "[game_version]"
- s += "Default" //Replace this with something else. Or ever better, delete it and uncomment the game version.
- s += ""
- s += ")"
-
- var/list/features = list()
-
- if(ticker)
- if(master_mode)
- features += master_mode
- else
- features += "STARTING"
-
- if (!config.enter_allowed)
- features += "closed"
-
- features += config.abandon_allowed ? "respawn" : "no respawn"
-
- if (config && config.allow_vote_mode)
- features += "vote"
-
- if (config && config.allow_ai)
- features += "AI allowed"
-
- var/n = 0
- for (var/mob/M in player_list)
- if (M.client)
- n++
-
- if (n > 1)
- features += "~[n] players"
- else if (n > 0)
- features += "~[n] player"
-
-
- if (config && config.hostedby)
- features += "hosted by [config.hostedby]"
-
- if (features)
- s += ": [jointext(features, ", ")]"
-
- /* does this help? I do not know */
- if (src.status != s)
- src.status = s
-
-#define FAILED_DB_CONNECTION_CUTOFF 5
-var/failed_db_connections = 0
-var/failed_old_db_connections = 0
-
-/hook/startup/proc/connectDB()
- if(!config.sql_enabled)
- world.log << "SQL connection disabled in config."
- else if(!setup_database_connection())
- world.log << "Your server failed to establish a connection with the feedback database."
- else
- world.log << "Feedback database connection established."
- return 1
-
-proc/setup_database_connection()
-
- if(failed_db_connections > FAILED_DB_CONNECTION_CUTOFF) //If it failed to establish a connection more than 5 times in a row, don't bother attempting to conenct anymore.
- return 0
-
- if(!dbcon)
- dbcon = new()
-
- var/user = sqlfdbklogin
- var/pass = sqlfdbkpass
- var/db = sqlfdbkdb
- var/address = sqladdress
- var/port = sqlport
-
- dbcon.Connect("dbi:mysql:[db]:[address]:[port]","[user]","[pass]")
- . = dbcon.IsConnected()
- if ( . )
- failed_db_connections = 0 //If this connection succeeded, reset the failed connections counter.
- else
- failed_db_connections++ //If it failed, increase the failed connections counter.
- world.log << dbcon.ErrorMsg()
-
- return .
-
-//This proc ensures that the connection to the feedback database (global variable dbcon) is established
-proc/establish_db_connection()
- if(failed_db_connections > FAILED_DB_CONNECTION_CUTOFF)
- return 0
-
- if(!dbcon || !dbcon.IsConnected())
- return setup_database_connection()
- else
- return 1
-
-
-/hook/startup/proc/connectOldDB()
- if(!config.sql_enabled)
- world.log << "SQL connection disabled in config."
- else if(!setup_old_database_connection())
- world.log << "Your server failed to establish a connection with the SQL database."
- else
- world.log << "SQL database connection established."
- return 1
-
-//These two procs are for the old database, while it's being phased out. See the tgstation.sql file in the SQL folder for more information.
-proc/setup_old_database_connection()
-
- if(failed_old_db_connections > FAILED_DB_CONNECTION_CUTOFF) //If it failed to establish a connection more than 5 times in a row, don't bother attempting to conenct anymore.
- return 0
-
- if(!dbcon_old)
- dbcon_old = new()
-
- var/user = sqllogin
- var/pass = sqlpass
- var/db = sqldb
- var/address = sqladdress
- var/port = sqlport
-
- dbcon_old.Connect("dbi:mysql:[db]:[address]:[port]","[user]","[pass]")
- . = dbcon_old.IsConnected()
- if ( . )
- failed_old_db_connections = 0 //If this connection succeeded, reset the failed connections counter.
- else
- failed_old_db_connections++ //If it failed, increase the failed connections counter.
- world.log << dbcon.ErrorMsg()
-
- return .
-
-//This proc ensures that the connection to the feedback database (global variable dbcon) is established
-proc/establish_old_db_connection()
- if(failed_old_db_connections > FAILED_DB_CONNECTION_CUTOFF)
- return 0
-
- if(!dbcon_old || !dbcon_old.IsConnected())
- return setup_old_database_connection()
- else
- return 1
-
-// Things to do when a new z-level was just made.
-/world/proc/max_z_changed()
- if(!islist(GLOB.players_by_zlevel))
- GLOB.players_by_zlevel = new /list(world.maxz, 0)
- while(GLOB.players_by_zlevel.len < world.maxz)
- GLOB.players_by_zlevel.len++
- GLOB.players_by_zlevel[GLOB.players_by_zlevel.len] = list()
-
-// Call this to make a new blank z-level, don't modify maxz directly.
-/world/proc/increment_max_z()
- maxz++
- max_z_changed()
-
-#undef FAILED_DB_CONNECTION_CUTOFF
diff --git a/polaris.dme b/polaris.dme
index 64db75b676..d8f680a899 100644
--- a/polaris.dme
+++ b/polaris.dme
@@ -211,6 +211,7 @@
#include "code\controllers\subsystems\processing\fastprocess.dm"
#include "code\controllers\subsystems\processing\obj.dm"
#include "code\controllers\subsystems\processing\processing.dm"
+#include "code\controllers\subsystems\processing\projectiles.dm"
#include "code\datums\ai_law_sets.dm"
#include "code\datums\ai_laws.dm"
#include "code\datums\beam.dm"
@@ -381,6 +382,7 @@
#include "code\game\skincmd.dm"
#include "code\game\sound.dm"
#include "code\game\trader_visit.dm"
+#include "code\game\world.dm"
#include "code\game\antagonist\_antagonist_setup.dm"
#include "code\game\antagonist\antagonist.dm"
#include "code\game\antagonist\antagonist_add.dm"
@@ -2308,6 +2310,7 @@
#include "code\modules\projectiles\projectile\magnetic.dm"
#include "code\modules\projectiles\projectile\pellets.dm"
#include "code\modules\projectiles\projectile\special.dm"
+#include "code\modules\projectiles\projectile\trace.dm"
#include "code\modules\projectiles\targeting\targeting_client.dm"
#include "code\modules\projectiles\targeting\targeting_gun.dm"
#include "code\modules\projectiles\targeting\targeting_mob.dm"