Exempt playtime by job basis (#1135)

<!-- Write **BELOW** The Headers and **ABOVE** The comments else it may
not be viewable. -->
<!-- You can view Contributing.MD for a detailed description of the pull
request process. -->

## About The Pull Request
Adds functionality for administrators to toggle playtime job exemption
for ckeys on a job basis
Admins go through player panel to access the menu and they can toggle
that with just one click
The action is logged, announced to all admins, and if the player is
logged on with that ckey, it is announced to him aswell
The exemption status is saved on the DB, a new `bubber_schema.sql` needs
to be ran to add the new table to the DB

![dreamseeker_gHffQnzd8E](https://github.com/Bubberstation/Bubberstation/assets/6381979/b726c730-45fd-4dac-a84b-df0a81dd375f)

![dreamseeker_wTJk1mZnTg](https://github.com/Bubberstation/Bubberstation/assets/6381979/b639fab4-69f1-4407-ba54-e015bb00a713)

![dreamseeker_MvNrETfYGk](https://github.com/Bubberstation/Bubberstation/assets/6381979/dbe6c384-2a00-4e6e-bb0b-56c311fc261a)

![heidisql_6YRaRhl2g3](https://github.com/Bubberstation/Bubberstation/assets/6381979/bd307ca6-be10-4da4-8e07-d6d431f4fa95)


<!-- Describe The Pull Request. Please be sure every change is
documented or this can delay review and even discourage maintainers from
merging your PR! -->

<!-- Please make sure to actually test your PRs. If you have not tested
your PR mention it. -->

## Why It's Good For The Game
It was requested

<!-- Argue for the merits of your changes and how they benefit the game,
especially if they are controversial and/or far reaching. If you can't
actually explain WHY what you are doing will improve the game, then it
probably isn't good for the game in the first place. -->

## Proof Of Testing

Included screenshots in the About section

<!-- Compile and run your code locally. Make sure it works. This is the
place to show off your changes! We are not responsible for testing your
features. -->

## Changelog

<!-- If your PR modifies aspects of the game that can be concretely
observed by players or admins you should add a changelog. If your change
does NOT meet this description, remove this section. Be sure to properly
mark your PRs to prevent unnecessary GBP loss. You can read up on GBP
and it's effects on PRs in the tgstation guides for contributors. Please
note that maintainers freely reserve the right to remove and add tags
should they deem it appropriate. You can attempt to finagle the system
all you want, but it's best to shoot for clear communication right off
the bat. -->

🆑
admin: administrators can now toggle job playtime exemption by job basis
for players
/🆑

<!-- Both 🆑's are required for the changelog to work! You can put
your name to the right of the first 🆑 if you want to overwrite your
GitHub username as author ingame. -->
<!-- You can use multiple of the same prefix (they're only used for the
icon ingame) and delete the unneeded ones. Despite some of the tags,
changelogs should generally represent how a player might be affected by
the changes rather than a summary of the PR's contents. -->

<!-- By opening a pull request. You have read and understood the
repository rules located on the main README.md on this project. -->

---------

Co-authored-by: Waterpig <49160555+Majkl-J@users.noreply.github.com>
This commit is contained in:
Azarak
2024-02-24 16:32:43 +01:00
committed by GitHub
parent d733f8b5bf
commit b5b8df0936
6 changed files with 190 additions and 0 deletions

24
SQL/bubber_schema.sql Normal file
View File

@@ -0,0 +1,24 @@
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
/*!40101 SET NAMES utf8 */;
/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;
/*!40103 SET TIME_ZONE='+00:00' */;
/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
--
-- Table structure for table `jobexempt`
--
DROP TABLE IF EXISTS `jobexempt`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `jobexempt` (
`ckey` varchar(32) NOT NULL,
`jobs` VARCHAR(2048) DEFAULT NULL,
PRIMARY KEY (`ckey`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
/*!40101 SET character_set_client = @saved_cs_client */;

View File

@@ -48,6 +48,15 @@
return
cmd_show_exp_panel(M.client)
// BUBBER EDIT START - Job exemption
else if (href_list["getjobexemptwindow"])
var/target_ckey = href_list["getjobexemptwindow"]
show_job_exempt_menu(usr, target_ckey)
else if (href_list["getjobexempttask"])
var/target_ckey = href_list["getjobexempttask"]
handle_job_exempt_menu_topic(usr, href, href_list, target_ckey)
// BUBBER EDIT END
// SKYRAT EDIT BEGIN -- ONE CLICK ANTAG
else if(href_list["makeAntag"])

View File

@@ -21,6 +21,9 @@
body += "\[<A href='?_src_=holder;[HrefToken()];editrights=[(GLOB.admin_datums[M.client.ckey] || GLOB.deadmins[M.client.ckey]) ? "rank" : "add"];key=[M.key]'>[M.client.holder ? M.client.holder.rank_names() : "Player"]</A>\]"
if(CONFIG_GET(flag/use_exp_tracking))
body += "\[<A href='?_src_=holder;[HrefToken()];getplaytimewindow=[REF(M)]'>" + M.client.get_exp_living(FALSE) + "</a>\]"
// BUBBERS EDIT START - Job exemption
body += "<br>\[<a href='?_src_=holder;[HrefToken()];getjobexemptwindow=[M.ckey]'>Job Exemption Menu</a>\]"
// BUBBERS EDIT END
if(isnewplayer(M))
body += " <B>Hasn't Entered Game</B> "

View File

@@ -17,6 +17,10 @@ GLOBAL_PROTECT(exp_to_update)
var/isexempt = C.prefs.db_flags & DB_FLAG_EXEMPT
if(isexempt)
return 0
// BUBBER EDIT START - Job exemption
if(is_job_exempt_from(C.ckey, title))
return 0
// BUBBER EDIT END
var/my_exp = C.calc_exp_type(get_exp_req_type())
var/job_requirement = get_exp_req_amount()
if(my_exp >= job_requirement)

View File

@@ -0,0 +1,149 @@
// Global cached list of job exemption to lower stress on the database
// Associative list of ckey to associative list of job titles which were granted the exemption ex. `job_exempt_cache[ckey][job_title]` being null is no exemption and being TRUE is an exemption
GLOBAL_LIST_INIT(job_exempt_cache, list())
/**
* Returns whether a ckey has the job exemption for a job
*
* Arguments:
* * ckey - The ckey you're checking, both key and ckey work
* * job_title - The `job.title` of the job you're checking
*/
/proc/is_job_exempt_from(ckey, job_title)
if(!SSdbcore.Connect())
return FALSE
// Convert to ckey just in case we get called with player 'key'
var/player_ckey = ckey(ckey)
// Load the ckey cache if it is not present
load_job_exempt_cache(player_ckey)
return GLOB.job_exempt_cache[player_ckey][job_title]
/**
* Loads job exempt cache for ckey, for internal use
*
* Arguments:
* * ckey - The ckey to load the cache for
*/
/proc/load_job_exempt_cache(ckey)
if(GLOB.job_exempt_cache[ckey])
return
GLOB.job_exempt_cache[ckey] = list()
// Load list from DB
var/datum/db_query/get_query = SSdbcore.NewQuery(
"SELECT jobs FROM [format_table_name("jobexempt")] WHERE ckey = :ckey",
list("ckey" = ckey)
)
if(!get_query.warn_execute())
qdel(get_query)
return
// Get first row value
if(!get_query.NextRow())
// If there's none then that means we havent inserted anything, so do that
var/datum/db_query/insert_query = SSdbcore.NewQuery(
"INSERT INTO [format_table_name("jobexempt")] (ckey, jobs) VALUES(:ckey, :jobs)",
list("ckey" = ckey, "jobs" = "")
)
insert_query.Execute()
qdel(insert_query)
qdel(get_query)
return
// We got a value, split the string and set the job titles to TRUE in our cache
var/list/string_list = splittext(get_query.item[1], ";")
for(var/job_title in string_list)
if(job_title == "")
continue
GLOB.job_exempt_cache[ckey][job_title] = TRUE
qdel(get_query)
/**
* Sets the job exempt state of a ckey to desired state
*
* Arguments:
* * ckey - The ckey to set the state for
* * job_title - The `job.title` of the state
* * state - New TRUE or FALSE state to set it to
*/
/proc/set_job_exempt_state(ckey, job_title, state)
if(!SSdbcore.Connect())
return
// Convert to ckey just in case we get called with player 'key'
var/player_ckey = ckey(ckey)
load_job_exempt_cache(player_ckey)
// Set cache
if(state)
GLOB.job_exempt_cache[player_ckey][job_title] = TRUE
else
GLOB.job_exempt_cache[player_ckey] -= job_title
// Set state in DB
var/joined_string = ""
for(var/title in GLOB.job_exempt_cache[player_ckey])
if(joined_string == "")
joined_string = title
else
joined_string += ";[title]"
var/datum/db_query/query = SSdbcore.NewQuery(
"UPDATE [format_table_name("jobexempt")] SET jobs = :jobs WHERE ckey = :ckey",
list("jobs" = joined_string, "ckey" = player_ckey)
)
query.Execute(FALSE)
qdel(query)
#define JOBS_PER_COLUMN 20
/**
* Displays the job exempt menu UI to target admin user
*
* Arguments:
* * user - The administrator mob to display the UI to
* * target_ckey - The ckey to inspect and possibly modify the job exemption statuses
*/
/proc/show_job_exempt_menu(mob/user, target_ckey)
if(!check_rights(R_ADMIN))
return
var/list/dat = list()
if(!SSdbcore.Connect())
dat += "Can't connect to the database"
else
dat += "Toggle playtime exemption for specific jobs for the ckey: [target_ckey]<HR>"
var/job_count = 0
dat += "<table width='100%'><tr><td width='33%' valign='top'>"
for(var/datum/job_department/department as anything in SSjob.joinable_departments)
if(job_count >= JOBS_PER_COLUMN)
job_count -= JOBS_PER_COLUMN
dat += "</td><td width='33%' valign='top'>"
dat += "<b>[department.department_name]</b><br>"
for(var/datum/job/job as anything in department.department_jobs)
job_count++
dat += "<a [is_job_exempt_from(target_ckey, job.title) ? "class='linkOn'" : ""] href='?_src_=holder;[HrefToken()];getjobexempttask=[target_ckey];task=togglejob;job=[job.title]'>[job.title]</a><br>"
dat += "</tr></td></table>"
var/datum/browser/popup = new(user, "job_exempt_menu", "Job Exemption Menu", 730, 650)
popup.set_content(dat.Join())
popup.open()
#undef JOBS_PER_COLUMN
/**
* Topic handler for the job exempt menu
*
* Arguments:
* * user - The administrator mob invoking the Topic
* * href - The href token
* * href_list - The href list
* * target_ckey - The ckey to edit
*/
/proc/handle_job_exempt_menu_topic(mob/user, href, href_list, target_ckey)
if(!check_rights(R_ADMIN))
return
switch(href_list["task"])
if("togglejob")
var/job_title = href_list["job"]
var/newstate = !is_job_exempt_from(target_ckey, job_title)
set_job_exempt_state(target_ckey, job_title, newstate)
// Announce to admins which admin is modifying whose exemptions
message_admins("[key_name_admin(user)] has [newstate ? "activated" : "deactivated"] job exp exempt status on ckey [target_ckey] for job: [job_title]")
log_admin("[key_name(user)] has [newstate ? "activated" : "deactivated"] job exp exempt status on ckey [target_ckey] for job: [job_title]")
// If a client whose job exemption is being modified, announce it to the player aswell
var/client/client = GLOB.directory[target_ckey]
if(client)
to_chat(client, span_boldnotice("Job exemption status for [job_title] has been [newstate ? "activated" : "deactivated"]"))
show_job_exempt_menu(user, target_ckey)

View File

@@ -8579,6 +8579,7 @@
#include "modular_zubbers\modules\hydroponics\code\grown\rocks.dm"
#include "modular_zubbers\modules\hydroponics\code\plantgenes\hydroponics.dm"
#include "modular_zubbers\modules\hydroponics\code\vending\megaseed.dm"
#include "modular_zubbers\modules\job_exempt\job_exempt.dm"
#include "modular_zubbers\modules\jobs\code\job_types\quartermaster.dm"
#include "modular_zubbers\modules\jobs\code\job_types\warden.dm"
#include "modular_zubbers\modules\jobs\code\trims\jobs.dm"