Skip to content

Commit

Permalink
Revert "del: SSblackbox system Removal" (#6456)
Browse files Browse the repository at this point in the history
Revert "del: SSblackbox system Removal (#6388)"

This reverts commit 314166e.
  • Loading branch information
BeebBeebBoob authored Jan 26, 2025
1 parent 314166e commit 9d88ff7
Show file tree
Hide file tree
Showing 119 changed files with 1,293 additions and 29 deletions.
205 changes: 205 additions & 0 deletions .github/USING_FEEDBACK_DATA.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,205 @@
# Using Feedback Data

## Introduction

`Feedback` is the name of the data storage system used for logging game statistics to the database. It is managed by `SSblackbox` and can be recorded in many formats. This guide will contain information on how to record feedback data properly, as well as what should and should not be recorded.

## Things you should and should not record

Feedback data can be useful, depending on how you use it. You need to be careful with what you record to make sure you are not saving useless data. Examples of good things to record:

- Antagonist win/loss rates if a new gamemode or antagonist is being added
- Department performance (IE: Slime cores produced in science)
- Basically anything which has actual meaning

Examples of bad things to record:

- Amount of times a wrench is used (Why)
- Hours spent on the server (We have other means of that)
- Basically, just think about it and ask yourself "Is this actually useful to base game design around"

Also note that feedback data **must** be anonymous. The only exception here is for data *anyone* on the server can see, such as round end antagonist reports.

## Feedback Data Recording

Feedback data can be reocrded in 5 formats. `amount`, `associative`, `nested tally`, `tally` and `text`.

### Amount

`amount` is the simplest form of feedback data recording. They are simply a numerical number which increase with each feedback increment. For example:

These DM calls:

```dm
SSblackbox.record_feedback("amount", "example", 8)
SSblackbox.record_feedback("amount", "example", 2)
// Note that you can use negative increments to decrease the value
```

Will produce the following JSON:

```json
{
"data":10
}
```

Notice the lack of any specific identification other than it being the value of the `data` key. Amounts are designed to be simple with minimal complexity, and are useful for logging statistics such as how many times one specific, distinct action is done (e.g.: How many MMIs have been filled). If you want to log multiple similar things (e.g.: How many mechas have been created, broken down by the mecha type), use a `tally` with a sub-key for each different mecha, instead of an amount with its own key per mecha.

### Associative

`associative` is used to record text that's associated with multiple key-value pairs. (e.g: coordinates). Further calls to the same key will append a new list to existing data. For example:

```dm
SSblackbox.record_feedback("associative", "example", 1, list("text" = "example", "path" = /obj/item, "number" = 4))
SSblackbox.record_feedback("associative", "example", 1, list("number" = 7, "text" = "example", "other text" = "sample"))
```

Will produce the following JSON:

```json
{
"data":{
"1":{
"text":"example",
"path":"/obj/item",
"number":"4"
},
"2":{
"number":"7",
"text":"example",
"other text":"sample"
}
}
}
```

Notice how everything is cast to strings, and each new entry added has its index increased ("1", "2", etc). Also take note how the `increment` parameter is not used here. It does nothing to the data, and `1` is used just as the value for consistency.

### Nested Tally

`nested tally` is used to track the number of occurances of structured semi-relational values (e.g.: the results of arcade machines). You can think of it as a running total, with the key being a list of strings (rather than a single string), with elements incrementally identifying the entity in question.

Technically, the values are nested in a multi-dimensional array. The final element in the data list is used as the tracking key, and all prior elements are used for nesting. Further calls to the same key will add or subtract from the saved value of the data key if it already exists in the same multi-dimensional position, and append the key and it's value if it doesn't exist already. This one is quite complicated, but an example is below:

```dm
SSblackbox.record_feedback("nested tally", "example", 1, list("fruit", "orange", "apricot"))
SSblackbox.record_feedback("nested tally", "example", 2, list("fruit", "orange", "orange"))
SSblackbox.record_feedback("nested tally", "example", 3, list("fruit", "orange", "apricot"))
SSblackbox.record_feedback("nested tally", "example", 10, list("fruit", "red", "apple"))
SSblackbox.record_feedback("nested tally", "example", 1, list("vegetable", "orange", "carrot"))
```

Will produce the following JSON:

```json
{
"data":{
"fruit":{
"orange":{
"apricot":4,
"orange":2
},
"red":{
"apple":10
}
},
"vegetable":{
"orange":{
"carrot":1
}
}
}
}
```

#### NOTE

Tracking values associated with a number can't merge with a nesting value, trying to do so will append the list

```dm
SSblackbox.record_feedback("nested tally", "example", 3, list("fruit", "orange"))
```

Will produce the following JSON:

```json
{
"data":{
"fruit":{
"orange":{
"apricot":4,
"orange":2
},
"red":{
"apple":10
},
"orange":3
},
"vegetable":{
"orange":{
"carrot":1
}
}
}
}
```

Avoid doing this, since having duplicate keys in JSON (data.fruit.orange) will break when parsing.

### Tally

`tally` is used to track the number of occurances of multiple related values (e.g.: how many times each type of gun is fired). Further calls to the same key will add or subtract from the saved value of the data key if it already exists, and append the key and it's value if it doesn't exist.

```dm
SSblackbox.record_feedback("tally", "example", 1, "sample data")
SSblackbox.record_feedback("tally", "example", 4, "sample data")
SSblackbox.record_feedback("tally", "example", 2, "other data")
```

Will produce the following JSON:

```json
{
"data":{
"sample data":5,
"other data":2
}
}
```

### Text

`text` is used for simple single-string records (e.g.: the current chaplain religion). Further calls to the same key will append saved data unless the overwrite argument is true or it already exists. When encoded, calls made with overwrite will lack square brackets.

```dm
SSblackbox.record_feedback("text", "example", 1, "sample text")
SSblackbox.record_feedback("text", "example", 1, "other text")
SSblackbox.record_feedback("text", "example", 1, "sample text")
```

Will produce the following JSON:

```json
{
"data":[
"sample text",
"other text"
]
}
```

Note how `"sample text"` only appears once. `text` is a set with no duplicates, instead of a list with duplicates. Also take note how the `increment` parameter is not used here. It does nothing to the data, and `1` is used just as the value for consistency.

## Feedback Versioning

If the logging content (i.e.: What data is logged) for a variable is ever changed, the version needs bumping. This can be done with the `versions` list on the subsystem definition itself. All values default to `1`.

```dm
var/list/versions = list(
"round_end_stats" = 4,
"admin_toggle" = 2,
"gun_fired" = 2)
```

If you are doing a type change (i.e.: Changing from a `tally` to a `nested tally`), **USE AN ENTIRELY NEW KEY NAME**.
46 changes: 46 additions & 0 deletions SQL/paradise_schema.sql
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,33 @@ CREATE TABLE `customuseritems` (
) ENGINE=MyISAM AUTO_INCREMENT=56 DEFAULT CHARSET=utf8mb4;
/*!40101 SET character_set_client = @saved_cs_client */;

--
-- Table structure for table `death`
--

DROP TABLE IF EXISTS `death`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `death` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`pod` text NOT NULL COMMENT 'Place of death',
`coord` text NOT NULL COMMENT 'X, Y, Z POD',
`tod` datetime NOT NULL COMMENT 'Time of death',
`job` text NOT NULL,
`special` text NOT NULL,
`name` text NOT NULL,
`byondkey` text NOT NULL,
`laname` text NOT NULL COMMENT 'Last attacker name',
`lakey` text NOT NULL COMMENT 'Last attacker key',
`gender` text NOT NULL,
`bruteloss` int(11) NOT NULL,
`brainloss` int(11) NOT NULL,
`fireloss` int(11) NOT NULL,
`oxyloss` int(11) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM AUTO_INCREMENT=166546 DEFAULT CHARSET=utf8mb4;
/*!40101 SET character_set_client = @saved_cs_client */;

--
-- Table structure for table `donators`
--
Expand Down Expand Up @@ -206,6 +233,25 @@ CREATE TABLE `ban` (
) ENGINE=InnoDB AUTO_INCREMENT=58903 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;

--
-- Table structure for table `feedback`
--

DROP TABLE IF EXISTS `feedback`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `feedback` (
`id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT,
`datetime` datetime NOT NULL,
`round_id` int(8) NOT NULL,
`key_name` varchar(32) NOT NULL,
`key_type` enum('text', 'amount', 'tally', 'nested tally', 'associative') NOT NULL,
`version` tinyint(3) UNSIGNED NOT NULL,
`json` LONGTEXT NOT NULL COLLATE 'utf8mb4_general_ci',
PRIMARY KEY (`id`)
) ENGINE=MyISAM AUTO_INCREMENT=257638 DEFAULT CHARSET=utf8mb4;
/*!40101 SET character_set_client = @saved_cs_client */;

--
-- Table structure for table `player`
--
Expand Down
7 changes: 0 additions & 7 deletions SQL/updates/34-35.sql

This file was deleted.

2 changes: 1 addition & 1 deletion code/__DEFINES/misc.dm
Original file line number Diff line number Diff line change
Expand Up @@ -363,7 +363,7 @@
#define EXPLOSION_BLOCK_PROC -1

// The SQL version required by this version of the code
#define SQL_VERSION 35
#define SQL_VERSION 34

// Vending machine stuff
#define CAT_NORMAL 1
Expand Down
7 changes: 4 additions & 3 deletions code/__DEFINES/subsystems.dm
Original file line number Diff line number Diff line change
Expand Up @@ -67,9 +67,10 @@
// Subsystems shutdown in the reverse of the order they initialize in
// The numbers just define the ordering, they are meaningless otherwise.
#define INIT_ORDER_TITLE 100 // This **MUST** load first or people will se blank lobby screens
#define INIT_ORDER_SPEECH_CONTROLLER 18
#define INIT_ORDER_GARBAGE 17
#define INIT_ORDER_DBCORE 16
#define INIT_ORDER_SPEECH_CONTROLLER 19
#define INIT_ORDER_GARBAGE 18
#define INIT_ORDER_DBCORE 17
#define INIT_ORDER_BLACKBOX 16
#define INIT_ORDER_CLEANUP 15
#define INIT_ORDER_INPUT 14
#define INIT_ORDER_SOUNDS 13
Expand Down
2 changes: 2 additions & 0 deletions code/controllers/master.dm
Original file line number Diff line number Diff line change
Expand Up @@ -329,6 +329,8 @@ GLOBAL_REAL(Master, /datum/controller/master) = new
var/time = rustg_time_milliseconds(SS_INIT_TIMER_KEY)
var/seconds = round(time / 1000, 0.01)

// Always update the blackbox tally regardless.
SSblackbox.record_feedback("tally", "subsystem_initialize", time, subsystem.name)

// Gave invalid return value.
if(result && !(result in valid_results))
Expand Down
9 changes: 9 additions & 0 deletions code/controllers/subsystem/chat.dm
Original file line number Diff line number Diff line change
Expand Up @@ -96,3 +96,12 @@ SUBSYSTEM_DEF(chat)

payload.resends += 1
send_payload_to_client(client, client_history[sequence])
SSblackbox.record_feedback(
"nested tally",
"chat_resend_byond_version",
1,
list(
"[client.byond_version]",
"[client.byond_build]",
),
)
1 change: 1 addition & 0 deletions code/controllers/subsystem/dbcore.dm
Original file line number Diff line number Diff line change
Expand Up @@ -581,6 +581,7 @@ SUBSYSTEM_DEF(dbcore)

log_admin("[key_name(usr)] is attempting to re-establish the DB Connection")
message_admins("[key_name_admin(usr)] is attempting to re-establish the DB Connection")
SSblackbox.record_feedback("tally", "admin_verb", 1, "Force Reconnect DB") //If you are copy-pasting this, ensure the 4th parameter is unique to the new proc!

SSdbcore.failed_connections = 0 // Reset this
if(!SSdbcore.Connect())
Expand Down
48 changes: 48 additions & 0 deletions code/controllers/subsystem/jobs.dm
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,8 @@ SUBSYSTEM_DEF(jobs)
//Shuffle players and jobs
unassigned = shuffle(unassigned)

HandleFeedbackGathering()

if(new_malf) // code to assign malf AI before civs.
Debug("DO, Running AI Check")
FillMalfAIPosition()
Expand Down Expand Up @@ -586,6 +588,52 @@ SUBSYSTEM_DEF(jobs)
J.spawn_positions = J.positions_lowpop
J.total_positions = J.positions_lowpop

/datum/controller/subsystem/jobs/proc/HandleFeedbackGathering()
for(var/datum/job/job in occupations)

var/high = 0 //high
var/medium = 0 //medium
var/low = 0 //low
var/never = 0 //never
var/banned = 0 //banned
var/young = 0 //account too young
var/charyoung = 0 //character too young
var/disabled = 0 //has disability rendering them ineligible
for(var/mob/new_player/player in GLOB.player_list)
if(!(player.ready && player.mind && !player.mind.assigned_role))
continue //This player is not ready
if(jobban_isbanned(player, job.title))
banned++
continue
if(!job.player_old_enough(player.client))
young++
continue
if(job.available_in_playtime(player.client))
young++
continue
if(job.barred_by_disability(player.client))
disabled++
continue
if(!job.character_old_enough(player.client))
charyoung++
continue
if(player.client.prefs.GetJobDepartment(job, 1) & job.flag)
high++
else if(player.client.prefs.GetJobDepartment(job, 2) & job.flag)
medium++
else if(player.client.prefs.GetJobDepartment(job, 3) & job.flag)
low++
else never++ //not selected

SSblackbox.record_feedback("nested tally", "job_preferences", high, list("[job.title]", "high"))
SSblackbox.record_feedback("nested tally", "job_preferences", medium, list("[job.title]", "medium"))
SSblackbox.record_feedback("nested tally", "job_preferences", low, list("[job.title]", "low"))
SSblackbox.record_feedback("nested tally", "job_preferences", never, list("[job.title]", "never"))
SSblackbox.record_feedback("nested tally", "job_preferences", banned, list("[job.title]", "banned"))
SSblackbox.record_feedback("nested tally", "job_preferences", young, list("[job.title]", "young"))
SSblackbox.record_feedback("nested tally", "job_preferences", disabled, list("[job.title]", "disabled"))
SSblackbox.record_feedback("nested tally", "job_preferences", charyoung, list("[job.title]", "charyoung"))


/datum/controller/subsystem/jobs/proc/CreateMoneyAccount(mob/living/H, rank, datum/job/job)
var/money_amount = rand(job.min_start_money, job.max_start_money)
Expand Down
Loading

0 comments on commit 9d88ff7

Please sign in to comment.