diff --git a/SQL/database_changelog.txt.rej b/SQL/database_changelog.txt.rej index 1b201ec4aa..f65c192173 100644 --- a/SQL/database_changelog.txt.rej +++ b/SQL/database_changelog.txt.rej @@ -1,15 +1,10 @@ diff a/SQL/database_changelog.txt b/SQL/database_changelog.txt (rejected hunks) -@@ -1,10 +1,10 @@ - Any time you make a change to the schema files, remember to increment the database schema version. Generally increment the minor number, major should be reserved for significant changes to the schema. Both values go up to 255. +@@ -5,7 +5,7 @@ Also, added flags column to the player table. --The latest database version is 3.1; The query to update the schema revision table is: -+The latest database version is 3.0; The query to update the schema revision table is: + CREATE TABLE `role_time` ( `ckey` VARCHAR(32) NOT NULL , `job` VARCHAR(128) NOT NULL , `minutes` INT UNSIGNED NOT NULL, PRIMARY KEY (`ckey`, `job`) ) ENGINE = InnoDB; --UPDATE `schema_revision` SET major = 3, minor = 1 LIMIT 1; -+INSERT INTO `schema_revision` (`major`, `minor`) VALUES (3, 0); - or --UPDATE `SS13_schema_revision` SET major = 3, minor = 1 LIMIT 1; -+INSERT INTO `SS13_schema_revision` (`major`, `minor`) VALUES (3, 0); +-ALTER TABLE `player` ADD `flags` INT NOT NULL AFTER `accountjoindate`; ++ALTER TABLE `player` ADD `flags` INT NOT NULL default '0' AFTER `accountjoindate`; - ---------------------------------------------------- + UPDATE `schema_revision` SET minor = 1; diff --git a/SQL/tgstation_schema.sql b/SQL/tgstation_schema.sql index 4c76e1f275..4c19d0bd20 100644 --- a/SQL/tgstation_schema.sql +++ b/SQL/tgstation_schema.sql @@ -257,6 +257,21 @@ CREATE TABLE `messages` ( ) ENGINE=InnoDB DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; +-- +-- Table structure for table `role_time` +-- + +DROP TABLE IF EXISTS `role_time`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; + +CREATE TABLE `role_time` +( `ckey` VARCHAR(32) NOT NULL , + `job` VARCHAR(32) NOT NULL , + `minutes` INT UNSIGNED NOT NULL, + PRIMARY KEY (`ckey`, `job`) + ) ENGINE = InnoDB; + -- -- Table structure for table `player` -- @@ -272,6 +287,7 @@ CREATE TABLE `player` ( `computerid` varchar(32) NOT NULL, `lastadminrank` varchar(32) NOT NULL DEFAULT 'Player', `accountjoindate` DATE DEFAULT NULL, + `flags` smallint(5) unsigned DEFAULT '0' NOT NULL, PRIMARY KEY (`ckey`), KEY `idx_player_cid_ckey` (`computerid`,`ckey`), KEY `idx_player_ip_ckey` (`ip`,`ckey`) diff --git a/SQL/tgstation_schema.sql.rej b/SQL/tgstation_schema.sql.rej new file mode 100644 index 0000000000..51068bed4e --- /dev/null +++ b/SQL/tgstation_schema.sql.rej @@ -0,0 +1,9 @@ +diff a/SQL/tgstation_schema.sql b/SQL/tgstation_schema.sql (rejected hunks) +@@ -268,7 +283,6 @@ CREATE TABLE `player` ( + `ip` int(10) unsigned NOT NULL, + `computerid` varchar(32) NOT NULL, + `lastadminrank` varchar(32) NOT NULL DEFAULT 'Player', +- `exp` mediumtext, + PRIMARY KEY (`id`), + UNIQUE KEY `ckey` (`ckey`), + KEY `idx_player_cid_ckey` (`computerid`,`ckey`), diff --git a/SQL/tgstation_schema_prefixed.sql b/SQL/tgstation_schema_prefixed.sql index f717bf96ad..094485df59 100644 --- a/SQL/tgstation_schema_prefixed.sql +++ b/SQL/tgstation_schema_prefixed.sql @@ -253,6 +253,21 @@ CREATE TABLE `SS13_messages` ( ) ENGINE=InnoDB DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; +-- +-- Table structure for table `SS13_role_time` +-- + +DROP TABLE IF EXISTS `SS13_role_time`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; + +CREATE TABLE `SS13_role_time` +( `ckey` VARCHAR(32) NOT NULL , + `job` VARCHAR(32) NOT NULL , + `minutes` INT UNSIGNED NOT NULL, + PRIMARY KEY (`ckey`, `job`) + ) ENGINE = InnoDB; + -- -- Table structure for table `SS13_player` -- @@ -268,6 +283,7 @@ CREATE TABLE `SS13_player` ( `computerid` varchar(32) NOT NULL, `lastadminrank` varchar(32) NOT NULL DEFAULT 'Player', `accountjoindate` DATE DEFAULT NULL, + `flags` smallint(5) unsigned DEFAULT '0' NOT NULL, PRIMARY KEY (`ckey`), KEY `idx_player_cid_ckey` (`computerid`,`ckey`), KEY `idx_player_ip_ckey` (`ip`,`ckey`) diff --git a/SQL/tgstation_schema_prefixed.sql.rej b/SQL/tgstation_schema_prefixed.sql.rej new file mode 100644 index 0000000000..dd4bc6a7f5 --- /dev/null +++ b/SQL/tgstation_schema_prefixed.sql.rej @@ -0,0 +1,9 @@ +diff a/SQL/tgstation_schema_prefixed.sql b/SQL/tgstation_schema_prefixed.sql (rejected hunks) +@@ -268,7 +297,6 @@ CREATE TABLE `SS13_player` ( + `ip` int(10) unsigned NOT NULL, + `computerid` varchar(32) NOT NULL, + `lastadminrank` varchar(32) NOT NULL DEFAULT 'Player', +- `exp` mediumtext, + PRIMARY KEY (`id`), + UNIQUE KEY `ckey` (`ckey`), + KEY `idx_player_cid_ckey` (`computerid`,`ckey`), diff --git a/code/__DEFINES/preferences.dm b/code/__DEFINES/preferences.dm index f26b750492..b6133df287 100644 --- a/code/__DEFINES/preferences.dm +++ b/code/__DEFINES/preferences.dm @@ -47,4 +47,22 @@ #define SEC_DEPT_ENGINEERING "Engineering" #define SEC_DEPT_MEDICAL "Medical" #define SEC_DEPT_SCIENCE "Science" -#define SEC_DEPT_SUPPLY "Supply" \ No newline at end of file +#define SEC_DEPT_SUPPLY "Supply" + +// Playtime tracking system, see jobs_exp.dm +#define EXP_TYPE_LIVING "Living" +#define EXP_TYPE_CREW "Crew" +#define EXP_TYPE_COMMAND "Command" +#define EXP_TYPE_ENGINEERING "Engineering" +#define EXP_TYPE_MEDICAL "Medical" +#define EXP_TYPE_SCIENCE "Science" +#define EXP_TYPE_SUPPLY "Supply" +#define EXP_TYPE_SECURITY "Security" +#define EXP_TYPE_SILICON "Silicon" +#define EXP_TYPE_SERVICE "Service" +#define EXP_TYPE_ANTAG "Antag" +#define EXP_TYPE_SPECIAL "Special" +#define EXP_TYPE_GHOST "Ghost" + +//Flags in the players table in the db +#define DB_FLAG_EXEMPT 1 \ No newline at end of file diff --git a/code/_compile_options.dm b/code/_compile_options.dm index a669f1b4f5..fe66b9bf69 100644 --- a/code/_compile_options.dm +++ b/code/_compile_options.dm @@ -72,4 +72,4 @@ //Update this whenever the db schema changes //make sure you add an update to the schema_version stable in the db changelog #define DB_MAJOR_VERSION 3 -#define DB_MINOR_VERSION 0 +#define DB_MINOR_VERSION 1 diff --git a/code/controllers/configuration.dm b/code/controllers/configuration.dm index abf2c1f937..daf0537547 100644 --- a/code/controllers/configuration.dm +++ b/code/controllers/configuration.dm @@ -103,6 +103,13 @@ var/use_account_age_for_jobs = 0 //Uses the time they made the account for the job restriction stuff. New player joining alerts should be unaffected. var/see_own_notes = 0 //Can players see their own admin notes (read-only)? Config option in config.txt + var/use_exp_tracking = FALSE + var/use_exp_restrictions_heads = FALSE + var/use_exp_restrictions_heads_hours = 0 + var/use_exp_restrictions_heads_department = FALSE + var/use_exp_restrictions_other = FALSE + var/use_exp_restrictions_admin_bypass = FALSE + //Population cap vars var/soft_popcap = 0 var/hard_popcap = 0 diff --git a/code/controllers/configuration.dm.rej b/code/controllers/configuration.dm.rej new file mode 100644 index 0000000000..5d5c2c5055 --- /dev/null +++ b/code/controllers/configuration.dm.rej @@ -0,0 +1,24 @@ +diff a/code/controllers/configuration.dm b/code/controllers/configuration.dm (rejected hunks) +@@ -337,17 +337,17 @@ + if("use_account_age_for_jobs") + use_account_age_for_jobs = 1 + if("use_exp_tracking") +- use_exp_tracking = 1 ++ use_exp_tracking = TRUE + if("use_exp_restrictions_heads") +- use_exp_restrictions_heads = 1 ++ use_exp_restrictions_heads = TRUE + if("use_exp_restrictions_heads_hours") + use_exp_restrictions_heads_hours = text2num(value) + if("use_exp_restrictions_heads_department") +- use_exp_restrictions_heads_department = 1 ++ use_exp_restrictions_heads_department = TRUE + if("use_exp_restrictions_other") +- use_exp_restrictions_other = 1 ++ use_exp_restrictions_other = TRUE + if("use_exp_restrictions_admin_bypass") +- use_exp_restrictions_admin_bypass = 1 ++ use_exp_restrictions_admin_bypass = TRUE + if("lobby_countdown") + lobby_countdown = text2num(value) + if("round_end_countdown") diff --git a/code/controllers/subsystem/blackbox.dm b/code/controllers/subsystem/blackbox.dm index 3344afcaaa..5f53f870dc 100644 --- a/code/controllers/subsystem/blackbox.dm +++ b/code/controllers/subsystem/blackbox.dm @@ -1,7 +1,7 @@ SUBSYSTEM_DEF(blackbox) name = "Blackbox" wait = 6000 - flags = SS_NO_TICK_CHECK | SS_NO_INIT + flags = SS_NO_TICK_CHECK runlevels = RUNLEVEL_GAME | RUNLEVEL_POSTGAME init_order = INIT_ORDER_BLACKBOX diff --git a/code/controllers/subsystem/blackbox.dm.rej b/code/controllers/subsystem/blackbox.dm.rej new file mode 100644 index 0000000000..5bd713172b --- /dev/null +++ b/code/controllers/subsystem/blackbox.dm.rej @@ -0,0 +1,10 @@ +diff a/code/controllers/subsystem/blackbox.dm b/code/controllers/subsystem/blackbox.dm (rejected hunks) +@@ -40,7 +40,7 @@ SUBSYSTEM_DEF(blackbox) + + if(config.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 +- SSblackbox.update_exp(10,FALSE) ++ update_exp(10,FALSE) + + + /datum/controller/subsystem/blackbox/Recover() diff --git a/code/controllers/subsystem/job.dm b/code/controllers/subsystem/job.dm index f28373ef22..1be36e5ac7 100644 --- a/code/controllers/subsystem/job.dm +++ b/code/controllers/subsystem/job.dm @@ -73,6 +73,8 @@ SUBSYSTEM_DEF(job) return 0 if(!job.player_old_enough(player.client)) return 0 + if(job.required_playtime_remaining(player.client)) + return 0 var/position_limit = job.total_positions if(!latejoin) position_limit = job.spawn_positions @@ -95,6 +97,9 @@ SUBSYSTEM_DEF(job) if(!job.player_old_enough(player.client)) Debug("FOC player not old enough, Player: [player]") continue + if(job.required_playtime_remaining(player.client)) + Debug("FOC player not enough xp, Player: [player]") + continue if(flag && (!(flag in player.client.prefs.be_special))) Debug("FOC flag failed, Player: [player], Flag: [flag], ") continue @@ -130,6 +135,10 @@ SUBSYSTEM_DEF(job) Debug("GRJ player not old enough, Player: [player]") continue + if(job.required_playtime_remaining(player.client)) + Debug("GRJ player not enough xp, Player: [player]") + continue + if(player.mind && job.title in player.mind.restricted_roles) Debug("GRJ incompatible with antagonist role, Player: [player], Job: [job.title]") continue @@ -300,6 +309,10 @@ SUBSYSTEM_DEF(job) Debug("DO player not old enough, Player: [player], Job:[job.title]") continue + if(job.required_playtime_remaining(player.client)) + Debug("DO player not enough xp, Player: [player], Job:[job.title]") + continue + if(player.mind && job.title in player.mind.restricted_roles) Debug("DO incompatible with antagonist role, Player: [player], Job:[job.title]") continue @@ -463,6 +476,9 @@ SUBSYSTEM_DEF(job) if(!job.player_old_enough(player.client)) level6++ continue + if(job.required_playtime_remaining(player.client)) + level6++ + continue if(player.client.prefs.GetJobDepartment(job, 1) & job.flag) level1++ else if(player.client.prefs.GetJobDepartment(job, 2) & job.flag) diff --git a/code/controllers/subsystem/server_maint.dm.rej b/code/controllers/subsystem/server_maint.dm.rej new file mode 100644 index 0000000000..486375b505 --- /dev/null +++ b/code/controllers/subsystem/server_maint.dm.rej @@ -0,0 +1,30 @@ +diff a/code/controllers/subsystem/server_maint.dm b/code/controllers/subsystem/server_maint.dm (rejected hunks) +@@ -6,18 +6,16 @@ SUBSYSTEM_DEF(server_maint) + flags = SS_POST_FIRE_TIMING|SS_FIRE_IN_LOBBY + priority = 10 + var/list/currentrun +- var/triggertime = null + + /datum/controller/subsystem/server_maint/Initialize(timeofday) + if (config.hub) + world.visibility = 1 +- triggertime = REALTIMEOFDAY + ..() + + /datum/controller/subsystem/server_maint/fire(resumed = FALSE) + if(!resumed) + src.currentrun = GLOB.clients.Copy() +- ++ + var/list/currentrun = src.currentrun + var/round_started = SSticker.HasRoundStarted() + +@@ -39,8 +37,3 @@ SUBSYSTEM_DEF(server_maint) + return + + #undef PING_BUFFER_TIME +- if(config.sql_enabled) +- sql_poll_population() +- if(config.use_exp_tracking) +- if(REALTIMEOFDAY > (triggertime +3000)) //server maint fires once at roundstart then once every 10 minutes. a 5 min check skips the first fire +- update_exp(10,0) diff --git a/code/datums/mind.dm b/code/datums/mind.dm index a31aab8607..7cc4558882 100644 --- a/code/datums/mind.dm +++ b/code/datums/mind.dm @@ -1468,6 +1468,7 @@ if(!(src in SSticker.mode.syndicates)) SSticker.mode.syndicates += src SSticker.mode.update_synd_icons_added(src) + assigned_role = "Syndicate" special_role = "Syndicate" SSticker.mode.forge_syndicate_objectives(src) SSticker.mode.greet_syndicate(src) @@ -1715,7 +1716,7 @@ /mob/living/carbon/human/mind_initialize() ..() if(!mind.assigned_role) - mind.assigned_role = "Assistant" //defualt + mind.assigned_role = "Unassigned" //default //XENO /mob/living/carbon/alien/mind_initialize() diff --git a/code/game/gamemodes/antag_spawner.dm.rej b/code/game/gamemodes/antag_spawner.dm.rej new file mode 100644 index 0000000000..ea9a00132a --- /dev/null +++ b/code/game/gamemodes/antag_spawner.dm.rej @@ -0,0 +1,9 @@ +diff a/code/game/gamemodes/antag_spawner.dm b/code/game/gamemodes/antag_spawner.dm (rejected hunks) +@@ -108,6 +108,7 @@ + new_objective.explanation_text = "Protect [usr.real_name], the wizard." + M.mind.objectives += new_objective + SSticker.mode.apprentices += M.mind ++ M.mind.assigned_role = "Apprentice" + M.mind.special_role = "apprentice" + SSticker.mode.update_wiz_icons_added(M.mind) + M << sound('sound/effects/magic.ogg') diff --git a/code/modules/admin/admin.dm b/code/modules/admin/admin.dm index 5943e753a0..e6a1b3d383 100644 --- a/code/modules/admin/admin.dm +++ b/code/modules/admin/admin.dm @@ -31,6 +31,8 @@ if(M.client) body += " played by [M.client] " body += "\[[M.client.holder ? M.client.holder.rank : "Player"]\]" + if(config.use_exp_tracking) + body += "\[" + M.client.get_exp_living() + "\]" if(isnewplayer(M)) body += " Hasn't Entered Game " @@ -442,7 +444,7 @@ if("Hard Restart (No Delay, No Feeback Reason)") world.Reboot() if("Hardest Restart (No actions, just reboot)") - world.Reboot(fast_track = TRUE) + world.Reboot(fast_track = TRUE) if("Service Restart (Force restart DD)") GLOB.reboot_mode = REBOOT_MODE_HARD world.ServiceReboot() diff --git a/code/modules/admin/admin_verbs.dm b/code/modules/admin/admin_verbs.dm index 3276a319db..6403c9028f 100644 --- a/code/modules/admin/admin_verbs.dm +++ b/code/modules/admin/admin_verbs.dm @@ -61,6 +61,7 @@ GLOBAL_LIST_INIT(admin_verbs_admin, world.AVerbsAdmin()) /client/proc/cmd_admin_local_narrate, /*sends text to all mobs within view of atom*/ /client/proc/cmd_admin_create_centcom_report, /client/proc/cmd_change_command_name, + /client/proc/cmd_admin_check_player_exp, /* shows players by playtime */ /client/proc/toggle_antag_hud, /*toggle display of the admin antag hud*/ /client/proc/toggle_AI_interact, /*toggle admin ability to interact with machines as an AI*/ /client/proc/customiseSNPC, /* Customise any interactive crewmembers in the world */ diff --git a/code/modules/admin/admin_verbs.dm.rej b/code/modules/admin/admin_verbs.dm.rej new file mode 100644 index 0000000000..c2f884d37c --- /dev/null +++ b/code/modules/admin/admin_verbs.dm.rej @@ -0,0 +1,10 @@ +diff a/code/modules/admin/admin_verbs.dm b/code/modules/admin/admin_verbs.dm (rejected hunks) +@@ -663,7 +664,7 @@ GLOBAL_LIST_INIT(admin_verbs_hideable, AVerbsHideable()) + + if(!holder) + return +- ++ + if(has_antag_hud()) + toggle_antag_hud() + diff --git a/code/modules/admin/topic.dm b/code/modules/admin/topic.dm index ac047429c8..85ae68dabc 100644 --- a/code/modules/admin/topic.dm +++ b/code/modules/admin/topic.dm @@ -22,6 +22,24 @@ else if(href_list["stickyban"]) stickyban(href_list["stickyban"],href_list) + else if(href_list["getplaytimewindow"]) + if(!check_rights(R_ADMIN)) + return + var/mob/M = locate(href_list["getplaytimewindow"]) in GLOB.mob_list + if(!M) + to_chat(usr, "ERROR: Mob not found.") + return + cmd_show_exp_panel(M.client) + + else if(href_list["toggleexempt"]) + if(!check_rights(R_ADMIN)) + return + var/client/C = locate(href_list["toggleexempt"]) in GLOB.clients + if(!C) + to_chat(usr, "ERROR: Client not found.") + return + toggle_exempt_status(C) + else if(href_list["makeAntag"]) if (!SSticker.mode) to_chat(usr, "Not until the round starts!") diff --git a/code/modules/admin/verbs/randomverbs.dm.rej b/code/modules/admin/verbs/randomverbs.dm.rej new file mode 100644 index 0000000000..9941251566 --- /dev/null +++ b/code/modules/admin/verbs/randomverbs.dm.rej @@ -0,0 +1,25 @@ +diff a/code/modules/admin/verbs/randomverbs.dm b/code/modules/admin/verbs/randomverbs.dm (rejected hunks) +@@ -1246,7 +1246,8 @@ GLOBAL_LIST_EMPTY(custom_outfits) //Admin created outfits + to_chat(usr, "ERROR: Client not found.") + return + +- C.set_db_player_flags() ++ if(!C.set_db_player_flags()) ++ to_chat(usr, "ERROR: Unable read player flags from database. Please check logs.") + var/dbflags = C.prefs.db_flags + var/newstate = FALSE + if(dbflags & DB_FLAG_EXEMPT) +@@ -1254,6 +1255,8 @@ GLOBAL_LIST_EMPTY(custom_outfits) //Admin created outfits + else + newstate = TRUE + +- message_admins("[key_name_admin(usr)] has [newstate ? "activated" : "deactivated"] job exp exempt status on [key_name_admin(C)]") +- log_admin("[key_name(usr)] has [newstate ? "activated" : "deactivated"] job exp exempt status on [key_name(C)]") +- C.update_flag_db(DB_FLAG_EXEMPT, newstate) +\ No newline at end of file ++ if(C.update_flag_db(DB_FLAG_EXEMPT, newstate)) ++ to_chat(usr, "ERROR: Unable to update player flags. Please check logs.") ++ else ++ message_admins("[key_name_admin(usr)] has [newstate ? "activated" : "deactivated"] job exp exempt status on [key_name_admin(C)]") ++ log_admin("[key_name(usr)] has [newstate ? "activated" : "deactivated"] job exp exempt status on [key_name(C)]") +\ No newline at end of file diff --git a/code/modules/client/preferences.dm b/code/modules/client/preferences.dm index 4c23db3f86..f7b2cb341b 100644 --- a/code/modules/client/preferences.dm +++ b/code/modules/client/preferences.dm @@ -36,6 +36,7 @@ GLOBAL_LIST_EMPTY(preferences_datums) var/tgui_lock = TRUE var/windowflashing = TRUE var/toggles = TOGGLES_DEFAULT + var/db_flags var/chat_toggles = TOGGLES_DEFAULT_CHAT var/ghost_form = "ghost" var/ghost_orbit = GHOST_ORBIT_CIRCLE @@ -580,6 +581,10 @@ GLOBAL_LIST_EMPTY(preferences_datums) if(jobban_isbanned(user, rank)) HTML += "[rank] BANNED" continue + var/required_playtime_remaining = job.required_playtime_remaining(user.client) + if(required_playtime_remaining) + HTML += "[rank] \[ [get_exp_format(required_playtime_remaining)] as [job.get_exp_req_type()] \] " + continue if(!job.player_old_enough(user.client)) var/available_in_days = job.available_in_days(user.client) HTML += "[rank] \[IN [(available_in_days)] DAYS\]" diff --git a/code/modules/client/preferences.dm.rej b/code/modules/client/preferences.dm.rej new file mode 100644 index 0000000000..02e6ad2d76 --- /dev/null +++ b/code/modules/client/preferences.dm.rej @@ -0,0 +1,14 @@ +diff a/code/modules/client/preferences.dm b/code/modules/client/preferences.dm (rejected hunks) +@@ -130,12 +130,6 @@ GLOBAL_LIST_EMPTY(preferences_datums) + menuoptions = list() + return + +-/datum/preferences/vv_edit_var(var_name, var_value) +- var/static/list/banned_edits = list("exp") +- if(var_name in banned_edits) +- return FALSE +- return ..() +- + /datum/preferences/proc/ShowChoices(mob/user) + if(!user || !user.client) + return diff --git a/code/modules/jobs/job_exp.dm b/code/modules/jobs/job_exp.dm new file mode 100644 index 0000000000..e65de15279 --- /dev/null +++ b/code/modules/jobs/job_exp.dm @@ -0,0 +1,194 @@ +GLOBAL_LIST_EMPTY(exp_to_update) +GLOBAL_PROTECT(exp_to_update) + + +// Procs +/datum/job/proc/required_playtime_remaining(client/C) + if(!C) + return 0 + if(!config.use_exp_tracking) + return 0 + if(!exp_requirements || !exp_type) + return 0 + if(!job_is_xp_locked(src.title)) + return 0 + if(config.use_exp_restrictions_admin_bypass && check_rights(R_ADMIN, FALSE, C.mob)) + return 0 + var/isexempt = C.prefs.db_flags & DB_FLAG_EXEMPT + if(isexempt) + return 0 + var/my_exp = C.calc_exp_type(get_exp_req_type()) + var/job_requirement = get_exp_req_amount() + if(my_exp >= job_requirement) + return 0 + else + return (job_requirement - my_exp) + +/datum/job/proc/get_exp_req_amount() + if(title in GLOB.command_positions) + if(config.use_exp_restrictions_heads_hours) + return config.use_exp_restrictions_heads_hours * 60 + return exp_requirements + +/datum/job/proc/get_exp_req_type() + if(title in GLOB.command_positions) + if(config.use_exp_restrictions_heads_department && exp_type_department) + return exp_type_department + return exp_type + +/proc/job_is_xp_locked(jobtitle) + if(!config.use_exp_restrictions_heads && jobtitle in GLOB.command_positions) + return FALSE + if(!config.use_exp_restrictions_other && !(jobtitle in GLOB.command_positions)) + return FALSE + return TRUE + +/client/proc/calc_exp_type(exptype) + var/list/explist = prefs.exp.Copy() + var/amount = 0 + var/list/typelist = GLOB.exp_jobsmap[exptype] + if(!typelist) + return -1 + for(var/job in typelist["titles"]) + if(job in explist) + amount += explist[job] + return amount + +/client/proc/get_exp_report() + if(!config.use_exp_tracking) + return "Tracking is disabled in the server configuration file." + var/list/play_records = prefs.exp + if(!play_records.len) + set_exp_from_db() + play_records = prefs.exp + if(!play_records.len) + return "[key] has no records." + var/return_text = list() + return_text += "" + var/list/jobs_locked = list() + var/list/jobs_unlocked = list() + for(var/datum/job/job in SSjob.occupations) + if(job.exp_requirements && job.exp_type) + if(!job_is_xp_locked(job.title)) + continue + else if(!job.required_playtime_remaining(mob.client)) + jobs_unlocked += job.title + else + var/xp_req = job.get_exp_req_amount() + jobs_locked += "[job.title] [get_exp_format(text2num(calc_exp_type(job.get_exp_req_type())))] / [get_exp_format(xp_req)] as [job.get_exp_req_type()])" + if(jobs_unlocked.len) + return_text += "

Jobs Unlocked:" + if(jobs_locked.len) + return_text += "

Jobs Not Unlocked:" + return return_text + + +/client/proc/get_exp_living() + if(!prefs.exp) + return "No data" + var/exp_living = text2num(prefs.exp[EXP_TYPE_LIVING]) + return get_exp_format(exp_living) + +/proc/get_exp_format(expnum) + if(expnum > 60) + return num2text(round(expnum / 60)) + "h" + else if(expnum > 0) + return num2text(expnum) + "m" + else + return "0h" + +/datum/controller/subsystem/blackbox/proc/update_exp(mins, ann = FALSE) + if(!SSdbcore.Connect()) + return -1 + for(var/client/L in GLOB.clients) + if(L.is_afk()) + continue + addtimer(CALLBACK(L,/client/proc/update_exp_list,mins,ann),10) + CHECK_TICK + +/proc/update_exp_db() + SSdbcore.MassInsert(format_table_name("role_time"),GLOB.exp_to_update,TRUE) + LAZYCLEARLIST(GLOB.exp_to_update) + +//Manual incrementing/updating +/* +/client/proc/update_exp_client(minutes, announce_changes = FALSE) + if(!src ||!ckey || !config.use_exp_tracking) + return + if(!SSdbcore.Connect()) + return -1 + var/datum/DBQuery/exp_read = SSdbcore.NewQuery("SELECT job, minutes FROM [format_table_name("role_time")] WHERE ckey = '[sanitizeSQL(ckey)]'") + if(!exp_read.Execute()) + var/err = exp_read.ErrorMsg() + log_sql("SQL ERROR during exp_update_client read. Error : \[[err]\]\n") + message_admins("SQL ERROR during exp_update_client read. Error : \[[err]\]\n") + return -1 + var/list/play_records = list() + while(exp_read.NextRow()) + play_records[exp_read.item[1]] = text2num(exp_read.item[2]) + + for(var/rtype in SSjob.name_occupations) + if(!play_records[rtype]) + play_records[rtype] = 0 + for(var/rtype in GLOB.exp_specialmap) + if(!play_records[rtype]) + play_records[rtype] = 0 + var/list/old_records = play_records.Copy() + if(mob.stat != DEAD && mob.mind.assigned_role) + play_records[EXP_TYPE_LIVING] += minutes + if(announce_changes) + to_chat(mob,"You got: [minutes] Living EXP!") + for(var/job in SSjob.name_occupations) + if(mob.mind.assigned_role == job) + play_records[job] += minutes + if(announce_changes) + to_chat(mob,"You got: [minutes] [job] EXP!") + if(mob.mind.special_role && !mob.mind.var_edited) + play_records[EXP_TYPE_SPECIAL] += minutes + if(announce_changes) + to_chat(mob,"You got: [minutes] [mob.mind.special_role] EXP!") + else if(isobserver(mob)) + play_records[EXP_TYPE_GHOST] += minutes + if(announce_changes) + to_chat(mob,"You got: [minutes] Ghost EXP!") + else if(minutes) //Let "refresh" checks go through + return + prefs.exp = play_records + + for(var/jtype in play_records) + var jobname = jtype + var time = play_records[jtype] + var/datum/DBQuery/update_query = SSdbcore.NewQuery("INSERT INTO [format_table_name("role_time")] (`ckey`, `job`, `minutes`) VALUES ('[sanitizeSQL(ckey)]', '[jobname]', '[time]') ON DUPLICATE KEY UPDATE time = '[time]'") + if(!update_query.Execute()) + var/err = update_queryd.ErrorMsg() + log_game("SQL ERROR during exp_update_client update. Error : \[[err]\]\n") + message_admins("SQL ERROR during exp_update_client update. Error : \[[err]\]\n") + return \ No newline at end of file diff --git a/code/modules/jobs/job_exp.dm.rej b/code/modules/jobs/job_exp.dm.rej new file mode 100644 index 0000000000..128a479acd --- /dev/null +++ b/code/modules/jobs/job_exp.dm.rej @@ -0,0 +1,108 @@ +diff a/code/modules/jobs/job_exp.dm b/code/modules/jobs/job_exp.dm (rejected hunks) +@@ -140,15 +140,12 @@ GLOBAL_PROTECT(exp_to_update) + //resets a client's exp to what was in the db. + /client/proc/set_exp_from_db() + if(!config.use_exp_tracking) +- return ++ return -1 + if(!SSdbcore.Connect()) + return -1 + var/datum/DBQuery/exp_read = SSdbcore.NewQuery("SELECT job, minutes FROM [format_table_name("role_time")] WHERE ckey = '[sanitizeSQL(ckey)]'") + if(!exp_read.Execute()) +- var/err = exp_read.ErrorMsg() +- log_sql("SQL ERROR during exp_update_client read. Error : \[[err]\]\n") +- message_admins("SQL ERROR during exp_update_client read. Error : \[[err]\]\n") +- return ++ return -1 + var/list/play_records = list() + while(exp_read.NextRow()) + play_records[exp_read.item[1]] = text2num(exp_read.item[2]) +@@ -172,42 +169,24 @@ GLOBAL_PROTECT(exp_to_update) + if(!set_db_player_flags()) + return -1 + +- var/datum/DBQuery/flag_read = SSdbcore.NewQuery("SELECT flags FROM [format_table_name("player")] WHERE ckey='[sanitizeSQL(ckey)]'") +- +- if(!flag_read.Execute()) +- var/err = flag_read.ErrorMsg() +- log_sql("SQL ERROR during player flags read. Error : \[[err]\]\n") +- message_admins("SQL ERROR during player flags read. Error : \[[err]\]\n") +- return +- +- var/playerflags = null +- if(flag_read.NextRow()) +- playerflags = text2num(flag_read.item[1]) +- +- if((playerflags & newflag) && !state) ++ if((prefs.db_flags & newflag) && !state) + prefs.db_flags &= ~newflag + else + prefs.db_flags |= newflag + + var/datum/DBQuery/flag_update = SSdbcore.NewQuery("UPDATE [format_table_name("player")] SET flags = '[prefs.db_flags]' WHERE ckey='[sanitizeSQL(ckey)]'") + +- + if(!flag_update.Execute()) +- var/err = flag_update.ErrorMsg() +- log_sql("SQL ERROR during exp_exempt update. Error : \[[err]\]\n") +- message_admins("SQL ERROR during exp_exempt update. Error : \[[err]\]\n") +- return ++ return -1 ++ + + /client/proc/update_exp_list(minutes, announce_changes = FALSE) + if(!config.use_exp_tracking) +- return ++ return -1 + if(!SSdbcore.Connect()) + return -1 + var/datum/DBQuery/exp_read = SSdbcore.NewQuery("SELECT job, minutes FROM [format_table_name("role_time")] WHERE ckey = '[sanitizeSQL(ckey)]'") + if(!exp_read.Execute()) +- var/err = exp_read.ErrorMsg() +- log_sql("SQL ERROR during exp_update_client read. Error : \[[err]\]\n") +- message_admins("SQL ERROR during exp_update_client read. Error : \[[err]\]\n") + return -1 + var/list/play_records = list() + while(exp_read.NextRow()) +@@ -241,9 +220,13 @@ GLOBAL_PROTECT(exp_to_update) + if(announce_changes) + to_chat(mob,"You got: [minutes] [role] EXP!") + if(mob.mind.special_role && !mob.mind.var_edited) +- play_records[mob.mind.special_role] += minutes ++ var/trackedrole = mob.mind.special_role ++ var/gangrole = lookforgangrole(mob.mind.special_role) ++ if(gangrole) ++ trackedrole = gangrole ++ play_records[trackedrole] += minutes + if(announce_changes) +- to_chat(src,"You got: [minutes] [mob.mind.special_role] EXP!") ++ to_chat(src,"You got: [minutes] [trackedrole] EXP!") + if(!rolefound) + play_records["Unknown"] += minutes + else +@@ -276,11 +259,21 @@ GLOBAL_PROTECT(exp_to_update) + var/datum/DBQuery/flags_read = SSdbcore.NewQuery("SELECT flags FROM [format_table_name("player")] WHERE ckey='[ckey]'") + + if(!flags_read.Execute()) +- var/err = flags_read.ErrorMsg() +- log_sql("SQL ERROR during player flags read. Error : \[[err]\]\n") +- message_admins("SQL ERROR during player flags read. Error : \[[err]\]\n") + return FALSE + + if(flags_read.NextRow()) + prefs.db_flags = text2num(flags_read.item[1]) ++ else if(isnull(prefs.db_flags)) ++ prefs.db_flags = 0 //This PROBABLY won't happen, but better safe than sorry. + return TRUE ++ ++//Since each gang is tracked as a different antag type, records need to be generalized or you get up to 57 different possible records ++/proc/lookforgangrole(rolecheck) ++ if(findtext(rolecheck,"Gangster")) ++ return "Gangster" ++ else if(findtext(rolecheck,"Gang Boss")) ++ return "Gang Boss" ++ else if(findtext(rolecheck,"Gang Lieutenant")) ++ return "Gang Lieutenant" ++ else ++ return FALSE +\ No newline at end of file diff --git a/code/modules/jobs/job_types/captain.dm b/code/modules/jobs/job_types/captain.dm index b9d01b7c02..47efea04ac 100755 --- a/code/modules/jobs/job_types/captain.dm +++ b/code/modules/jobs/job_types/captain.dm @@ -13,6 +13,8 @@ Captain selection_color = "#ccccff" req_admin_notify = 1 minimal_player_age = 14 + exp_requirements = 180 + exp_type = EXP_TYPE_CREW outfit = /datum/outfit/job/captain @@ -64,6 +66,9 @@ Head of Personnel selection_color = "#ddddff" req_admin_notify = 1 minimal_player_age = 10 + exp_requirements = 180 + exp_type = EXP_TYPE_CREW + exp_type_department = EXP_TYPE_SUPPLY outfit = /datum/outfit/job/hop diff --git a/code/modules/jobs/job_types/engineering.dm b/code/modules/jobs/job_types/engineering.dm index 40800998c0..4070a00e97 100644 --- a/code/modules/jobs/job_types/engineering.dm +++ b/code/modules/jobs/job_types/engineering.dm @@ -14,6 +14,9 @@ Chief Engineer selection_color = "#ffeeaa" req_admin_notify = 1 minimal_player_age = 7 + exp_requirements = 180 + exp_type = EXP_TYPE_CREW + exp_type_department = EXP_TYPE_ENGINEERING outfit = /datum/outfit/job/ce @@ -72,6 +75,8 @@ Station Engineer spawn_positions = 5 supervisors = "the chief engineer" selection_color = "#fff5cc" + exp_requirements = 60 + exp_type = EXP_TYPE_CREW outfit = /datum/outfit/job/engineer @@ -127,6 +132,8 @@ Atmospheric Technician spawn_positions = 2 supervisors = "the chief engineer" selection_color = "#fff5cc" + exp_requirements = 60 + exp_type = EXP_TYPE_CREW outfit = /datum/outfit/job/atmos diff --git a/code/modules/jobs/job_types/job.dm b/code/modules/jobs/job_types/job.dm index 0a1622626c..4e57096994 100644 --- a/code/modules/jobs/job_types/job.dm +++ b/code/modules/jobs/job_types/job.dm @@ -43,6 +43,11 @@ var/outfit = null + var/exp_requirements = 0 + + var/exp_type = "" + var/exp_type_department = "" + //Only override this proc //H is usually a human unless an /equip override transformed it /datum/job/proc/after_spawn(mob/living/H, mob/M) diff --git a/code/modules/jobs/job_types/medical.dm b/code/modules/jobs/job_types/medical.dm index 196f74764f..6b72d851a5 100644 --- a/code/modules/jobs/job_types/medical.dm +++ b/code/modules/jobs/job_types/medical.dm @@ -14,6 +14,9 @@ Chief Medical Officer selection_color = "#ffddf0" req_admin_notify = 1 minimal_player_age = 7 + exp_requirements = 180 + exp_type = EXP_TYPE_CREW + exp_type_department = EXP_TYPE_MEDICAL outfit = /datum/outfit/job/cmo @@ -90,6 +93,8 @@ Chemist spawn_positions = 2 supervisors = "the chief medical officer" selection_color = "#ffeef0" + exp_type = EXP_TYPE_CREW + exp_requirements = 60 outfit = /datum/outfit/job/chemist @@ -124,6 +129,8 @@ Geneticist spawn_positions = 2 supervisors = "the chief medical officer and research director" selection_color = "#ffeef0" + exp_type = EXP_TYPE_CREW + exp_requirements = 60 outfit = /datum/outfit/job/geneticist @@ -158,6 +165,8 @@ Virologist spawn_positions = 1 supervisors = "the chief medical officer" selection_color = "#ffeef0" + exp_type = EXP_TYPE_CREW + exp_requirements = 60 outfit = /datum/outfit/job/virologist diff --git a/code/modules/jobs/job_types/science.dm b/code/modules/jobs/job_types/science.dm index aa9dd1b179..68d3bdeca9 100644 --- a/code/modules/jobs/job_types/science.dm +++ b/code/modules/jobs/job_types/science.dm @@ -14,6 +14,9 @@ Research Director selection_color = "#ffddff" req_admin_notify = 1 minimal_player_age = 7 + exp_type_department = EXP_TYPE_SCIENCE + exp_requirements = 180 + exp_type = EXP_TYPE_CREW outfit = /datum/outfit/job/rd @@ -68,6 +71,8 @@ Scientist spawn_positions = 3 supervisors = "the research director" selection_color = "#ffeeff" + exp_requirements = 60 + exp_type = EXP_TYPE_CREW outfit = /datum/outfit/job/scientist @@ -101,6 +106,8 @@ Roboticist spawn_positions = 2 supervisors = "research director" selection_color = "#ffeeff" + exp_requirements = 60 + exp_type = EXP_TYPE_CREW outfit = /datum/outfit/job/roboticist diff --git a/code/modules/jobs/job_types/security.dm b/code/modules/jobs/job_types/security.dm index 88c73db19a..64b3c0ea8e 100644 --- a/code/modules/jobs/job_types/security.dm +++ b/code/modules/jobs/job_types/security.dm @@ -20,6 +20,9 @@ Head of Security selection_color = "#ffdddd" req_admin_notify = 1 minimal_player_age = 14 + exp_requirements = 300 + exp_type = EXP_TYPE_CREW + exp_type_department = EXP_TYPE_SECURITY outfit = /datum/outfit/job/hos @@ -71,6 +74,8 @@ Warden supervisors = "the head of security" selection_color = "#ffeeee" minimal_player_age = 7 + exp_requirements = 300 + exp_type = EXP_TYPE_CREW outfit = /datum/outfit/job/warden @@ -121,6 +126,8 @@ Detective supervisors = "the head of security" selection_color = "#ffeeee" minimal_player_age = 7 + exp_requirements = 300 + exp_type = EXP_TYPE_CREW outfit = /datum/outfit/job/detective @@ -169,6 +176,8 @@ Security Officer supervisors = "the head of security, and the head of your assigned department (if applicable)" selection_color = "#ffeeee" minimal_player_age = 7 + exp_requirements = 300 + exp_type = EXP_TYPE_CREW outfit = /datum/outfit/job/security diff --git a/code/modules/jobs/job_types/silicon.dm b/code/modules/jobs/job_types/silicon.dm index 0b6380bfaf..2e0a710d41 100644 --- a/code/modules/jobs/job_types/silicon.dm +++ b/code/modules/jobs/job_types/silicon.dm @@ -12,6 +12,8 @@ AI supervisors = "your laws" req_admin_notify = 1 minimal_player_age = 30 + exp_requirements = 180 + exp_type = EXP_TYPE_CREW /datum/job/ai/equip(mob/living/carbon/human/H) return H.AIize(FALSE) @@ -44,6 +46,8 @@ Cyborg supervisors = "your laws and the AI" //Nodrak selection_color = "#ddffdd" minimal_player_age = 21 + exp_requirements = 120 + exp_type = EXP_TYPE_CREW /datum/job/cyborg/equip(mob/living/carbon/human/H) return H.Robotize(FALSE, FALSE) diff --git a/code/modules/jobs/jobs.dm b/code/modules/jobs/jobs.dm index 2d7caa5fc4..b5ec8fa27a 100644 --- a/code/modules/jobs/jobs.dm +++ b/code/modules/jobs/jobs.dm @@ -59,6 +59,26 @@ GLOBAL_LIST_INIT(nonhuman_positions, list( "Cyborg", "pAI")) +GLOBAL_LIST_INIT(exp_jobsmap, list( + EXP_TYPE_CREW = list("titles" = command_positions | engineering_positions | medical_positions | science_positions | supply_positions | security_positions | civilian_positions | list("AI","Cyborg")), // crew positions + EXP_TYPE_COMMAND = list("titles" = command_positions), + EXP_TYPE_ENGINEERING = list("titles" = engineering_positions), + EXP_TYPE_MEDICAL = list("titles" = medical_positions), + EXP_TYPE_SCIENCE = list("titles" = science_positions), + EXP_TYPE_SUPPLY = list("titles" = supply_positions), + EXP_TYPE_SECURITY = list("titles" = security_positions), + EXP_TYPE_SILICON = list("titles" = list("AI","Cyborg")), + EXP_TYPE_SERVICE = list("titles" = civilian_positions), +)) + +GLOBAL_LIST_INIT(exp_specialmap, list( + EXP_TYPE_LIVING = list(), // all living mobs + EXP_TYPE_ANTAG = list(), + EXP_TYPE_SPECIAL = list("Lifebringer","Ash Walker","Exile","Servant Golem","Free Golem","Hermit","Translocated Vet","Escaped Prisoner","Hotel Staff","SuperFriend","Space Syndicate","Ancient Crew","Space Doctor","Space Bartender","Beach Bum","Skeleton","Zombie","Space Bar Patron","Lavaland Syndicate","Ghost Role"), // Ghost roles + EXP_TYPE_GHOST = list() // dead people, observers +)) +GLOBAL_PROTECT(exp_jobsmap) +GLOBAL_PROTECT(exp_specialmap) /proc/guest_jobbans(job) return ((job in GLOB.command_positions) || (job in GLOB.nonhuman_positions) || (job in GLOB.security_positions)) diff --git a/code/modules/mob/dead/new_player/login.dm b/code/modules/mob/dead/new_player/login.dm index 9a49c23cfc..4de3b1d4dd 100644 --- a/code/modules/mob/dead/new_player/login.dm +++ b/code/modules/mob/dead/new_player/login.dm @@ -1,4 +1,7 @@ /mob/dead/new_player/Login() + if(config.use_exp_tracking) + client.set_exp_from_db() + client.set_db_player_flags() if(!mind) mind = new /datum/mind(key) mind.active = 1 diff --git a/code/modules/mob/dead/new_player/login.dm.rej b/code/modules/mob/dead/new_player/login.dm.rej new file mode 100644 index 0000000000..b7361e13ff --- /dev/null +++ b/code/modules/mob/dead/new_player/login.dm.rej @@ -0,0 +1,9 @@ +diff a/code/modules/mob/dead/new_player/login.dm b/code/modules/mob/dead/new_player/login.dm (rejected hunks) +@@ -1,5 +1,6 @@ + /mob/dead/new_player/Login() +- client.update_exp_client(0, 0) ++ if(config.use_exp_tracking) ++ client.update_exp_client(0, 0) + if(!mind) + mind = new /datum/mind(key) + mind.active = 1 diff --git a/code/modules/mob/dead/new_player/new_player.dm b/code/modules/mob/dead/new_player/new_player.dm index 2cea8e32f2..96e24dee83 100644 --- a/code/modules/mob/dead/new_player/new_player.dm +++ b/code/modules/mob/dead/new_player/new_player.dm @@ -307,6 +307,8 @@ return 0 if(!job.player_old_enough(src.client)) return 0 + if(job.required_playtime_remaining(client)) + return 0 if(config.enforce_human_authority && !client.prefs.pref_species.qualifies_for_rank(rank, client.prefs.features)) return 0 return 1 diff --git a/config/config.txt.rej b/config/config.txt.rej new file mode 100644 index 0000000000..fee79cf699 --- /dev/null +++ b/config/config.txt.rej @@ -0,0 +1,25 @@ +diff a/config/config.txt b/config/config.txt (rejected hunks) +@@ -32,17 +32,17 @@ BAN_LEGACY_SYSTEM + ## Uncomment this to have the job system use the player's account creation date, rather than the when they first joined the server for job timers. + #USE_ACCOUNT_AGE_FOR_JOBS + +-##Unhash this to track player playtime in the database. Requires database to be enabled. ++## Unhash this to track player playtime in the database. Requires database to be enabled. + #USE_EXP_TRACKING +-##Unhash this to enable playtime requirements for head jobs. ++## Unhash this to enable playtime requirements for head jobs. + #USE_EXP_RESTRICTIONS_HEADS +-##Unhash this to override head jobs' playtime requirements with this number of hours. ++## Unhash this to override head jobs' playtime requirements with this number of hours. + #USE_EXP_RESTRICTIONS_HEADS_HOURS 15 +-##Unhash this to change head jobs' playtime requirements so that they're based on department playtime, rather than crew playtime. ++## Unhash this to change head jobs' playtime requirements so that they're based on department playtime, rather than crew playtime. + #USE_EXP_RESTRICTIONS_HEADS_DEPARTMENT +-##Unhash this to enable playtime requirements for certain non-head jobs, like Engineer and Scientist. ++## Unhash this to enable playtime requirements for certain non-head jobs, like Engineer and Scientist. + #USE_EXP_RESTRICTIONS_OTHER +-##Allows admins to bypass job playtime requirements. ++## Allows admins to bypass job playtime requirements. + #USE_EXP_RESTRICTIONS_ADMIN_BYPASS + + diff --git a/tgstation.dme b/tgstation.dme index 1475d2335c..31f9768bc1 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -1449,6 +1449,7 @@ #include "code\modules\hydroponics\grown\tomato.dm" #include "code\modules\hydroponics\grown\towercap.dm" #include "code\modules\jobs\access.dm" +#include "code\modules\jobs\job_exp.dm" #include "code\modules\jobs\jobs.dm" #include "code\modules\jobs\job_types\assistant.dm" #include "code\modules\jobs\job_types\captain.dm"