Adds a Fulpstation Changelog (#1345)

* Commits a highly incomplete version of a Fulpstation changelog.

* Further develops a basic, copied version of /tg/'s changelog.

* Makes the changelog actually semi-functional.

* Finalizes a few things...
This commit is contained in:
QuiteLiterallyAnything
2025-03-02 01:49:49 -08:00
committed by GitHub
parent 5a63a340bf
commit 16aeb51d7b
8 changed files with 479 additions and 3 deletions

View File

@@ -249,6 +249,11 @@ GLOBAL_PROTECT(tracy_init_reason)
var/latest_changelog = file("[global.config.directory]/../html/changelogs/archive/" + time2text(world.timeofday, "YYYY-MM") + ".yml")
GLOB.changelog_hash = fexists(latest_changelog) ? md5(latest_changelog) : 0 //for telling if the changelog has changed recently
// FULP EDIT //
var/latest_fulp_changelog = file("[global.config.directory]/../fulp_modules/data/html/changelogs/archive/" + time2text(world.timeofday, "YYYY-MM") + ".yml")
GLOB.fulp_changelog_hash = fexists(latest_fulp_changelog) ? md5(latest_fulp_changelog) : 0
// FULP EDIT END //
if(GLOB.round_id)
log_game("Round ID: [GLOB.round_id]")

View File

@@ -21,6 +21,11 @@ GLOBAL_LIST_EMPTY(preferences_datums)
/// Cached changelog size, to detect new changelogs since last join
var/lastchangelog = ""
/// FULP EDIT ///
/// Same as var above but for Fulpstation's changelog.
var/last_fulp_changelog = ""
/// FULP EDIT END ///
/// List of ROLE_X that the client wants to be eligible for
var/list/be_special = list() //Special role selection

View File

@@ -0,0 +1,88 @@
//-----------------------------------------//
// Fulpstation's Changelog //
//-----------------------------------------//
/***
* This file contains all DM code related to Fulpstation's changelog.
*
* Most of this is just a very rough copying of existing /tg/ code with "fulp" appended to it,
* so credit for all of it goes to the various people who made /tg/'s changelog.
**/
/// FULP CHANGELOG DATUM ///
GLOBAL_DATUM(fulp_changelog_tgui, /datum/fulp_changelog)
GLOBAL_VAR_INIT(fulp_changelog_hash, "")
/datum/fulp_changelog
var/static/list/fulp_changelog_items = list()
/datum/fulp_changelog/ui_state()
return GLOB.always_state
/datum/fulp_changelog/ui_interact(mob/user, datum/tgui/ui)
ui = SStgui.try_update_ui(user, src, ui)
if (!ui)
ui = new(user, src, "FulpChangelog")
ui.open()
/datum/fulp_changelog/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state)
. = ..()
if(.)
return
if(action == "get_month")
var/datum/asset/fulp_changelog_item/fulp_changelog_item = fulp_changelog_items[params["date"]]
if (!fulp_changelog_item)
fulp_changelog_item = new /datum/asset/fulp_changelog_item(params["date"])
fulp_changelog_items[params["date"]] = fulp_changelog_item
return ui.send_asset(fulp_changelog_item)
/datum/fulp_changelog/ui_static_data()
var/list/data = list( "dates" = list() )
var/regex/ymlRegex = regex(@"\.yml", "g")
for(var/archive_file in sort_list(flist("fulp_modules/data/html/changelogs/archive/")))
var/archive_date = ymlRegex.Replace(archive_file, "")
data["dates"] = list(archive_date) + data["dates"]
return data
/// CHANGELOG VERB ///
/client/verb/fulp_changelog()
set name = "Changelog"
set category = "OOC"
if(!GLOB.fulp_changelog_tgui)
GLOB.fulp_changelog_tgui = new /datum/fulp_changelog()
GLOB.fulp_changelog_tgui.ui_interact(mob)
if(prefs.last_fulp_changelog != GLOB.fulp_changelog_hash)
prefs.last_fulp_changelog = GLOB.fulp_changelog_hash
prefs.save_preferences()
winset(src, "infowindow.changelog", "font-style=;")
/// FULP CHANGELOG ITEM ASSET ///
/datum/asset/fulp_changelog_item
_abstract = /datum/asset/fulp_changelog_item
var/item_filename
/datum/asset/fulp_changelog_item/New(date)
item_filename = SANITIZE_FILENAME("[date].yml")
SSassets.transport.register_asset(item_filename, file("fulp_modules/data/html/changelogs/archive/" + item_filename))
/datum/asset/fulp_changelog_item/send(client)
if (!item_filename)
return
. = SSassets.transport.send_assets(client, item_filename)
/datum/asset/fulp_changelog_item/get_url_mappings()
if (!item_filename)
return
. = list("[item_filename]" = SSassets.transport.get_asset_url(item_filename))
// See 'world.dm' for a changelog-related Fulp edit. //

View File

@@ -1,7 +1,5 @@
## List of all TG edits:
- .github/workflows/compile_changelogs.yml > Same as above.
- code/datums/greyscale/_greyscale_config.dm > Adds our greyscales folder to the sanity check
- code/game/area/areas/shuttles.dm > Plays ApproachingFulp instead of ApproachingTG
@@ -18,6 +16,14 @@
- tools/pull_request_hooks/autoChangelog.js > Changes changelog folder to fulp_modules/data/html/changelogs, to preserve them across TGUs.
- .github\workflows\compile_changelogs.yml > Same as above.
- code\game\world.dm > Marked by a "FULP EDIT" comment; copies a bit of code to make 'GLOB.fulp_changelog_hash' functional.
- code\modules\client\preferences.dm > Marked by a "FULP EDIT" comment; gives clients the 'last_fulp_changelog' var.
- interface\interface.dm > Changes the value of 'name' on '/client/verb/changelog' to "/TG/ Changelog"
## All Fulp files not contained within /fulp_modules/
- code/__DEFINES/fulp_defines > Contains all of our defines

View File

@@ -99,7 +99,7 @@
/client/verb/changelog()
set name = "Changelog"
set name = "/TG/ Changelog"
set category = "OOC"
if(!GLOB.changelog_tgui)
GLOB.changelog_tgui = new /datum/changelog()

View File

@@ -6526,6 +6526,7 @@
#include "fulp_modules\_signals\bloodsuckers.dm"
#include "fulp_modules\_signals\misc.dm"
#include "fulp_modules\_signals\nanites.dm"
#include "fulp_modules\data\html\changelogs\fulp_changelog.dm"
#include "fulp_modules\features\admin_chicanery\smites\catification.dm"
#include "fulp_modules\features\antagonists\antag_tips\antag_tip_integration.dm"
#include "fulp_modules\features\antagonists\antag_tips\preference.dm"

View File

@@ -0,0 +1,371 @@
import dateformat from 'dateformat';
import yaml from 'js-yaml';
import { Component, Fragment } from 'react';
import {
Box,
Button,
Dropdown,
Icon,
Section,
Stack,
Table,
} from '../tgui/components';
import { classes } from 'common/react';
import { resolveAsset } from '../tgui/assets';
import { useBackend } from '../tgui/backend';
import { Window } from '../tgui/layouts';
const icons = {
add: { icon: 'check-circle', color: 'green' },
admin: { icon: 'user-shield', color: 'purple' },
balance: { icon: 'balance-scale-right', color: 'yellow' },
bugfix: { icon: 'bug', color: 'green' },
code_imp: { icon: 'code', color: 'green' },
config: { icon: 'cogs', color: 'purple' },
expansion: { icon: 'check-circle', color: 'green' },
experiment: { icon: 'radiation', color: 'yellow' },
image: { icon: 'image', color: 'green' },
imageadd: { icon: 'tg-image-plus', color: 'green' },
imagedel: { icon: 'tg-image-minus', color: 'red' },
qol: { icon: 'hand-holding-heart', color: 'green' },
refactor: { icon: 'tools', color: 'green' },
rscadd: { icon: 'check-circle', color: 'green' },
rscdel: { icon: 'times-circle', color: 'red' },
server: { icon: 'server', color: 'purple' },
sound: { icon: 'volume-high', color: 'green' },
soundadd: { icon: 'tg-sound-plus', color: 'green' },
sounddel: { icon: 'tg-sound-minus', color: 'red' },
spellcheck: { icon: 'spell-check', color: 'green' },
map: { icon: 'map', color: 'green' },
tgs: { icon: 'toolbox', color: 'purple' },
tweak: { icon: 'wrench', color: 'green' },
unknown: { icon: 'info-circle', color: 'label' },
wip: { icon: 'hammer', color: 'orange' },
};
export class FulpChangelog extends Component {
constructor(props) {
super(props);
this.state = {
data: 'Loading changelog data...',
selectedDate: '',
selectedIndex: 0,
};
this.dateChoices = [];
}
setData(data) {
this.setState({ data });
}
setSelectedDate(selectedDate) {
this.setState({ selectedDate });
}
setSelectedIndex(selectedIndex) {
this.setState({ selectedIndex });
}
getData = (date, attemptNumber = 1) => {
const { act } = useBackend();
const self = this;
const maxAttempts = 6;
if (attemptNumber > maxAttempts) {
return this.setData(
'Failed to load data after ' + maxAttempts + ' attempts',
);
}
act('get_month', { date });
fetch(resolveAsset(date + '.yml')).then(async (changelogData) => {
const result = await changelogData.text();
const errorRegex = /^Cannot find/;
if (errorRegex.test(result)) {
const timeout = 50 + attemptNumber * 50;
self.setData('Loading changelog data' + '.'.repeat(attemptNumber + 3));
setTimeout(() => {
self.getData(date, attemptNumber + 1);
}, timeout);
} else {
self.setData(yaml.load(result, { schema: yaml.CORE_SCHEMA }));
}
});
};
componentDidMount() {
const {
data: { dates = [] },
} = useBackend();
if (dates) {
dates.forEach((date) =>
this.dateChoices.push(dateformat(date, 'mmmm yyyy', true)),
);
this.setSelectedDate(this.dateChoices[0]);
this.getData(dates[0]);
}
}
render() {
const { data, selectedDate, selectedIndex } = this.state;
const {
data: { dates },
} = useBackend();
const { dateChoices } = this;
const dateDropdown = dateChoices.length > 0 && (
<Stack mb={1}>
<Stack.Item>
<Button
className="Changelog__Button"
disabled={selectedIndex === 0}
icon={'chevron-left'}
onClick={() => {
const index = selectedIndex - 1;
this.setData('Loading changelog data...');
this.setSelectedIndex(index);
this.setSelectedDate(dateChoices[index]);
window.scrollTo(
0,
document.body.scrollHeight ||
document.documentElement.scrollHeight,
);
return this.getData(dates[index]);
}}
/>
</Stack.Item>
<Stack.Item>
<Dropdown
autoScroll={false}
options={dateChoices}
onSelected={(value) => {
const index = dateChoices.indexOf(value);
this.setData('Loading changelog data...');
this.setSelectedIndex(index);
this.setSelectedDate(value);
window.scrollTo(
0,
document.body.scrollHeight ||
document.documentElement.scrollHeight,
);
return this.getData(dates[index]);
}}
selected={selectedDate}
width="150px"
/>
</Stack.Item>
<Stack.Item>
<Button
className="Changelog__Button"
disabled={selectedIndex === dateChoices.length - 1}
icon={'chevron-right'}
onClick={() => {
const index = selectedIndex + 1;
this.setData('Loading changelog data...');
this.setSelectedIndex(index);
this.setSelectedDate(dateChoices[index]);
window.scrollTo(
0,
document.body.scrollHeight ||
document.documentElement.scrollHeight,
);
return this.getData(dates[index]);
}}
/>
</Stack.Item>
</Stack>
);
const header = (
<Section>
<h1>Fulpstation</h1>
<p>
<b>Please note: </b>
this changelog would not be possible without the groundwork laid by
/tg/station's contributors and so many others. Aside from its logged
content, this changelog is an almost identical copy of /tg/station's,
which can currently be found in the OOC tab under the "/TG/ Changelog"
verb.
</p>
<p>
<b>Thanks to: </b>
/tg/station, Baystation 12, /vg/station, NTstation, CDK Station devs,
FacepunchStation, GoonStation devs, the original Space Station 13
developers, GitHub user celotajstg for adapting this changelog into
TGUI, and countless others who have contributed to the game, issue
tracker or wiki over the years.
</p>
<p>
{'Recent GitHub contributors can be found '}
<a href="https://github.com/fulpstation/fulpstation/pulse/monthly">
here
</a>
.
</p>
<p>
{
'You can also find a link to our discord at the front page of our wiki'
}
<a href="https://wiki.fulp.gg/"> here</a>.
</p>
{dateDropdown}
</Section>
);
const footer = (
<Section>
{dateDropdown}
<h3>GoonStation 13 Development Team</h3>
<p>
<b>Coders: </b>
Stuntwaffle, Showtime, Pantaloons, Nannek, Keelin, Exadv1, hobnob,
Justicefries, 0staf, sniperchance, AngriestIBM, BrianOBlivion
</p>
<p>
<b>Spriters: </b>
Supernorn, Haruhi, Stuntwaffle, Pantaloons, Rho, SynthOrange, I Said
No
</p>
<p>
Traditional Games Space Station 13 is thankful to the GoonStation 13
Development Team for its work on the game up to the
{' r4407 release. The changelog for changes up to r4407 can be seen '}
<a href="https://wiki.ss13.co/Pre-2016_Changelog#April_2010">here</a>.
</p>
<p>
{'Except where otherwise noted, Goon Station 13 is licensed under a '}
<a href="https://creativecommons.org/licenses/by-nc-sa/3.0/">
Creative Commons Attribution-Noncommercial-Share Alike 3.0 License
</a>
{'. Rights are currently extended to '}
<a href="http://forums.somethingawful.com/">SomethingAwful Goons</a>
{' only.'}
</p>
<h3>Traditional Games Space Station 13 License</h3>
<p>
{'All code after '}
<a
href={
'https://github.com/tgstation/tgstation/commit/' +
'333c566b88108de218d882840e61928a9b759d8f'
}
>
commit 333c566b88108de218d882840e61928a9b759d8f on 2014/31/12 at
4:38 PM PST
</a>
{' is licensed under '}
<a href="https://www.gnu.org/licenses/agpl-3.0.html">GNU AGPL v3</a>
{'. All code before that commit is licensed under '}
<a href="https://www.gnu.org/licenses/gpl-3.0.html">GNU GPL v3</a>
{', including tools unless their readme specifies otherwise. See '}
<a href="https://github.com/tgstation/tgstation/blob/master/LICENSE">
LICENSE
</a>
{' and '}
<a href="https://github.com/tgstation/tgstation/blob/master/GPLv3.txt">
GPLv3.txt
</a>
{' for more details.'}
</p>
<p>
The TGS DMAPI API is licensed as a subproject under the MIT license.
{' See the footer of '}
<a
href={
'https://github.com/tgstation/tgstation/blob/master' +
'/code/__DEFINES/tgs.dm'
}
>
code/__DEFINES/tgs.dm
</a>
{' and '}
<a
href={
'https://github.com/tgstation/tgstation/blob/master' +
'/code/modules/tgs/LICENSE'
}
>
code/modules/tgs/LICENSE
</a>
{' for the MIT license.'}
</p>
<p>
{'All assets including icons and sound are under a '}
<a href="https://creativecommons.org/licenses/by-sa/3.0/">
Creative Commons 3.0 BY-SA license
</a>
{' unless otherwise indicated.'}
</p>
</Section>
);
const changes =
typeof data === 'object' &&
Object.keys(data).length > 0 &&
Object.entries(data)
.reverse()
.map(([date, authors]) => (
<Section key={date} title={dateformat(date, 'd mmmm yyyy', true)}>
<Box ml={3}>
{Object.entries(authors).map(([name, changes]) => (
<Fragment key={name}>
<h4>{name} changed:</h4>
<Box ml={3}>
<Table>
{changes.map((change) => {
const changeType = Object.keys(change)[0];
return (
<Table.Row key={changeType + change[changeType]}>
<Table.Cell
className={classes([
'Changelog__Cell',
'Changelog__Cell--Icon',
])}
>
<Icon
color={
icons[changeType]
? icons[changeType].color
: icons['unknown'].color
}
name={
icons[changeType]
? icons[changeType].icon
: icons['unknown'].icon
}
/>
</Table.Cell>
<Table.Cell className="Changelog__Cell">
{change[changeType]}
</Table.Cell>
</Table.Row>
);
})}
</Table>
</Box>
</Fragment>
))}
</Box>
</Section>
));
return (
<Window title="Changelog" width={675} height={650}>
<Window.Content scrollable>
{header}
{changes}
{typeof data === 'string' && <p>{data}</p>}
{footer}
</Window.Content>
</Window>
);
}
}