1
0
mirror of https://github.com/tobspr/shapez.io.git synced 2025-12-13 10:11:50 +00:00

Proper mods ui

This commit is contained in:
tobspr 2022-01-14 12:34:02 +01:00
parent 6fa2515d85
commit 4176621b05
14 changed files with 431 additions and 63 deletions

BIN
res/ui/icons/mods.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

BIN
res/ui/icons/mods_white.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

View File

@ -31,6 +31,7 @@
@import "states/mobile_warning";
@import "states/changelog";
@import "states/puzzle_menu";
@import "states/mods";
@import "ingame_hud/buildings_toolbar";
@import "ingame_hud/building_placer";

View File

@ -185,11 +185,14 @@
.updateLabel {
position: absolute;
transform: translateX(50%) rotate(-5deg);
color: #300bff;
@include Heading;
color: #fff;
@include PlainText;
font-weight: bold;
@include S(right, 40px);
@include S(bottom, 20px);
background: $modsColor;
@include S(border-radius, $globalBorderRadius);
@include S(padding, 0, 5px, 1px, 5px);
@include InlineAnimation(1.3s ease-in-out infinite) {
50% {
@ -305,14 +308,36 @@
@include S(padding-bottom, 10px);
@include S(border-radius, $globalBorderRadius);
h3 {
.header {
display: flex;
width: 100%;
align-items: center;
@include S(margin-bottom, 10px);
.editMods {
margin-left: auto;
@include S(width, 20px);
@include S(height, 20px);
padding: 0;
opacity: 0.5;
background: transparent center center/ 80% no-repeat;
& {
/* @load-async */
background-image: uiResource("icons/edit_key.png") !important;
}
}
}
h3 {
@include Heading;
color: $modsColor;
margin: 0;
}
.dlcHint {
@include SuperSmallText;
@include S(margin-top, 10px);
@include S(width, 300px);
display: grid;
grid-template-columns: 1fr auto;
@ -326,6 +351,7 @@
@include S(border-radius, $globalBorderRadius);
@include S(padding, 5px);
box-sizing: border-box;
@include PlainText;
@include S(margin-bottom, 5px);
display: grid;
grid-template-columns: 1fr auto auto;
@ -375,6 +401,7 @@
display: flex;
flex-direction: column;
align-items: center;
width: 100%;
}
.modeButtons {
@ -419,27 +446,33 @@
}
}
.importButton {
.outer {
@include S(margin-top, 15px);
}
.importButton {
@include IncreasedClickArea(0px);
}
.newGameButton {
@include IncreasedClickArea(0px);
@include S(margin-top, 15px);
@include S(margin-left, 15px);
}
.playModeButton {
.modsButton {
@include IncreasedClickArea(0px);
@include S(margin-top, 15px);
@include S(margin-left, 15px);
}
.editModeButton {
@include IncreasedClickArea(0px);
@include S(margin-top, 15px);
@include S(margin-left, 15px);
@include S(width, 20px);
& {
/* @load-async */
background-image: uiResource("res/ui/icons/mods_white.png") !important;
}
background-position: center center;
background-size: D(15px);
background-color: $modsColor !important;
background-repeat: no-repeat;
}
.savegames {

131
src/css/states/mods.scss Normal file
View File

@ -0,0 +1,131 @@
#state_ModsState {
.mainContent {
display: flex;
flex-direction: column;
}
> .headerBar {
display: grid;
grid-template-columns: 1fr auto;
align-items: center;
> h1 {
justify-self: start;
}
.openModsFolder {
background-color: $modsColor;
}
}
.noModSupport {
display: flex;
align-items: center;
justify-content: center;
height: 100%;
flex-direction: column;
.steamLink {
@include S(height, 50px);
@include S(width, 220px);
background: #171a23 center center / contain no-repeat;
overflow: hidden;
display: block;
text-indent: -999em;
cursor: pointer;
@include S(margin-top, 30px);
pointer-events: all;
transition: all 0.12s ease-in;
transition-property: opacity, transform;
@include S(border-radius, $globalBorderRadius);
&:hover {
opacity: 0.9;
}
}
}
.modsStats {
@include PlainText;
color: $accentColorDark;
&.noMods {
@include S(width, 400px);
align-self: center;
justify-self: center;
text-align: center;
display: flex;
flex-direction: column;
align-items: center;
@include Text;
@include S(margin-top, 100px);
color: lighten($accentColorDark, 15);
&::before {
@include S(margin-bottom, 15px);
content: "";
@include S(width, 50px);
@include S(height, 50px);
background-position: center center;
background-size: contain;
opacity: 0.2;
}
&::before {
/* @load-async */
background-image: uiResource("res/ui/icons/mods.png") !important;
}
}
}
.modsList {
@include S(margin-top, 10px);
overflow-y: scroll;
pointer-events: all;
@include S(padding-right, 5px);
flex-grow: 1;
.mod {
@include S(border-radius, $globalBorderRadius);
background: $accentColorBright;
@include S(margin-bottom, 4px);
@include S(padding, 7px);
@include S(grid-gap, 5px);
display: grid;
grid-template-columns: 1fr D(100px) D(100px) D(100px);
.checkbox {
align-self: center;
justify-self: center;
}
.mainInfo {
display: flex;
flex-direction: column;
.description {
@include SuperSmallText;
@include S(margin-top, 5px);
color: $accentColorDark;
}
.website {
text-transform: uppercase;
align-self: start;
@include SuperSmallText;
@include S(margin-top, 5px);
}
}
.version,
.author {
display: flex;
flex-direction: column;
strong {
text-transform: uppercase;
color: $accentColorDark;
@include SuperSmallText;
}
}
}
}
}

View File

@ -15,20 +15,21 @@
}
.sidebar {
display: grid;
display: flex;
@include S(min-width, 210px);
@include S(max-width, 320px);
@include S(grid-gap, 3px);
grid-template-rows: auto auto auto auto auto 1fr;
flex-direction: column;
@include StyleBelowWidth($layoutBreak) {
grid-template-rows: 1fr 1fr;
grid-template-columns: auto auto;
display: grid;
grid-template-columns: 1fr 1fr 1fr;
@include S(grid-gap, 5px);
max-width: unset !important;
}
button {
text-align: left;
@include S(margin-bottom, 3px);
&::after {
content: unset;
}
@ -37,15 +38,26 @@
@include StyleBelowWidth($layoutBreak) {
text-align: center;
height: D(30px) !important;
padding: D(5px) !important;
}
}
.other {
@include S(margin-top, 10px);
align-self: end;
margin-top: auto;
@include StyleBelowWidth($layoutBreak) {
margin-top: 0;
display: grid;
grid-template-columns: 1fr 1fr;
@include S(grid-gap, 5px);
max-width: unset !important;
grid-column: 1 / 3;
button {
margin: 0 !important;
}
}
}
@ -69,6 +81,30 @@
}
}
button.manageMods {
background-color: lighten($modsColor, 38);
color: $modsColor;
display: flex;
@include S(padding-right, 5px);
.newBadge {
color: #fff;
@include S(border-radius, $globalBorderRadius);
background: $modsColor;
margin-left: auto;
@include S(padding, 0, 3px, 0, 3px);
@include InlineAnimation(1.3s ease-in-out infinite) {
50% {
transform: rotate(0deg) scale(1.1);
}
}
}
&.active {
background-color: $colorGreenBright;
}
}
button.privacy {
@include S(margin-top, 4px);
}

View File

@ -38,6 +38,7 @@ $colorRedBright: #ef5072;
$colorOrangeBright: #ef9d50;
$themeColor: #393747;
$ingameHudBg: rgba(#333438, 0.9);
$modsColor: rgb(214, 60, 228);
$text3dColor: #f4ffff;

View File

@ -37,6 +37,7 @@ import { LoginState } from "./states/login";
import { WegameSplashState } from "./states/wegame_splash";
import { MODS } from "./mods/modloader";
import { MOD_SIGNALS } from "./mods/mod_signals";
import { ModsState } from "./states/mods";
/**
* @typedef {import("./platform/achievement_provider").AchievementProviderInterface} AchievementProviderInterface
@ -171,6 +172,7 @@ export class Application {
ChangelogState,
PuzzleMenuState,
LoginState,
ModsState,
];
for (let i = 0; i < states.length; ++i) {

View File

@ -16,11 +16,12 @@ registerMod(shapez => {
super(
app,
{
authorContact: "tobias@tobspr.io",
authorName: "tobspr",
website: "https://tobspr.io",
author: "tobspr",
name: "Demo Mod",
version: "1",
id: "demo-mod",
description: "A simple mod to demonstrate the capatibilities of the mod loader.",
},
modLoader
);
@ -28,11 +29,11 @@ registerMod(shapez => {
init() {
// Add some custom css
this.modInterface.registerCss(`
* {
font-family: "Comic Sans", "Comic Sans MS", Tahoma !important;
}
`);
// this.modInterface.registerCss(`
// * {
// font-family: "Comic Sans", "Comic Sans MS", Tahoma !important;
// }
// `);
// Replace a builtin sprite
["red", "green", "blue", "yellow", "purple", "cyan", "white"].forEach(color => {

View File

@ -12,8 +12,9 @@ export class Mod {
* @param {object} metadata
* @param {string} metadata.name
* @param {string} metadata.version
* @param {string} metadata.authorName
* @param {string} metadata.authorContact
* @param {string} metadata.author
* @param {string} metadata.website
* @param {string} metadata.description
* @param {string} metadata.id
*
* @param {ModLoader} modLoader

View File

@ -155,36 +155,22 @@ export class MainMenuState extends GameState {
? `
<div class="modsList">
<h3>${T.mainMenu.mods.title}
</h3>
<div class="header">
<h3>${T.mods.title}</h3>
<button class="styledButton editMods"></button>
</div>
<div class="modsList">
${MODS.mods
.map(mod => {
return `
<div class="mod">
<span class="name">${mod.metadata.name}</span>
<span class="version">${T.mainMenu.mods.version.replace(
"<version>",
mod.metadata.version
)}</span>
<span class="author">${T.mainMenu.mods.author.replace(
"<author>",
mod.metadata.authorName
)}</span>
</div>
<div class="mod">${mod.metadata.name} @ v${mod.metadata.version}</div>
`;
})
.join("")}
</div>
<div class="dlcHint">
${T.mainMenu.modsWarningPuzzleDLC}
<button class="styledButton modsOpenFolder">${
T.mainMenu.mods.openFolder
}</button>
${T.mainMenu.mods.warningPuzzleDLC}
</div>
@ -381,7 +367,7 @@ export class MainMenuState extends GameState {
".puzzleDlcPlayButton": this.onPuzzleModeButtonClicked,
".puzzleDlcGetButton": this.onPuzzleWishlistButtonClicked,
".wegameDisclaimer > .rating": this.onWegameRatingClicked,
".modsOpenFolder": this.openModsFolder,
".editMods": this.onModsClicked,
};
for (const key in clickHandling) {
@ -430,6 +416,14 @@ export class MainMenuState extends GameState {
this.trackClicks(playBtn, this.onPlayButtonClicked);
buttonContainer.appendChild(importButtonElement);
}
// Mods
const modsBtn = makeButton(
this.htmlElement.querySelector(".mainContainer .outer"),
["modsButton", "styledButton"],
"&nbsp;"
);
this.trackClicks(modsBtn, this.onModsClicked);
}
onPuzzleModeButtonClicked(force = false) {
@ -742,6 +736,12 @@ export class MainMenuState extends GameState {
);
}
onModsClicked() {
this.moveToState("ModsState", {
backToStateId: "MainMenuState",
});
}
onContinueButtonClicked() {
let latestLastUpdate = 0;
let latestInternalId;
@ -763,14 +763,6 @@ export class MainMenuState extends GameState {
});
}
openModsFolder() {
if (!G_IS_STANDALONE) {
this.dialogs.showWarning(T.global.error, T.mainMenu.mods.folderOnlyStandalone);
return;
}
getIPCRenderer().send("open-mods-folder");
}
onLeave() {
this.dialogs.cleanup();
}

135
src/js/states/mods.js Normal file
View File

@ -0,0 +1,135 @@
import { THIRDPARTY_URLS } from "../core/config";
import { TextualGameState } from "../core/textual_game_state";
import { getIPCRenderer } from "../core/utils";
import { MODS } from "../mods/modloader";
import { T } from "../translations";
export class ModsState extends TextualGameState {
constructor() {
super("ModsState");
}
getStateHeaderTitle() {
return T.mods.title;
}
internalGetFullHtml() {
let headerHtml = `
<div class="headerBar">
<h1><button class="backButton"></button> ${this.getStateHeaderTitle()}</h1>
<div class="actions">
${
G_IS_STANDALONE || G_IS_DEV
? `<button class="styledButton openModsFolder">${T.mods.openFolder}</button>`
: ""
}
</div>
</div>`;
return `
${headerHtml}
<div class="container">
${this.getInnerHTML()}
</div>
`;
}
getMainContentHTML() {
if (!G_IS_STANDALONE && !G_IS_DEV) {
return `
<div class="noModSupport">
<p>${T.mods.noModSupport}</p>
<a href="#" class="steamLink steam_2_npr" target="_blank">Get the shapez.io standalone!</a>
</div>
`;
}
if (MODS.mods.length === 0) {
return `
<div class="modsStats noMods">
${T.mods.modsInfo}
</div>
`;
}
let modsHtml = ``;
MODS.mods.forEach(mod => {
modsHtml += `
<div class="mod">
<div class="mainInfo">
<span class="name">${mod.metadata.name}</span>
<span class="description">${mod.metadata.description}</span>
<a class="website" href="${mod.metadata.website}" target="_blank">Website</a>
</div>
<span class="version"><strong>${T.mods.version}</strong>${mod.metadata.version}</span>
<span class="author"><strong>${T.mods.author}</strong>${mod.metadata.author}</span>
<div class="value checkbox checked">
<span class="knob"></span>
</div>
</div>
`;
});
return `
<div class="modsStats">
${T.mods.modsInfo}
</div>
<div class="modsList">
${modsHtml}
</div>
`;
}
onEnter() {
const steamLink = this.htmlElement.querySelector(".steamLink");
if (steamLink) {
this.trackClicks(steamLink, this.onSteamLinkClicked);
}
const openModsFolder = this.htmlElement.querySelector(".openModsFolder");
if (openModsFolder) {
this.trackClicks(openModsFolder, this.openModsFolder);
}
const checkboxes = this.htmlElement.querySelectorAll(".checkbox");
Array.from(checkboxes).forEach(checkbox => {
this.trackClicks(checkbox, this.showModTogglingComingSoon);
});
}
showModTogglingComingSoon() {
this.dialogs.showWarning(T.mods.togglingComingSoon.title, T.mods.togglingComingSoon.description);
}
openModsFolder() {
if (!G_IS_STANDALONE) {
this.dialogs.showWarning(T.global.error, T.mods.folderOnlyStandalone);
return;
}
getIPCRenderer().send("open-mods-folder");
}
onSteamLinkClicked() {
this.app.analytics.trackUiClick("mods_steam_link");
this.app.platformWrapper.openExternalLink(
THIRDPARTY_URLS.stanaloneCampaignLink + "/shapez_modsettings"
);
return false;
}
getDefaultPreviousState() {
return "SettingsState";
}
}

View File

@ -19,6 +19,8 @@ export class SettingsState extends TextualGameState {
<div class="sidebar">
${this.getCategoryButtonsHtml()}
${
this.app.platformWrapper.getSupportsKeyboard()
? `
@ -28,6 +30,18 @@ export class SettingsState extends TextualGameState {
: ""
}
${
G_WEGAME_VERSION
? ""
: `
<button class="styledButton categoryButton manageMods">${T.mods.title}
<span class="newBadge">${T.settings.newBadge}</span>
</button>
`
}
<div class="other">
${
@ -131,6 +145,11 @@ export class SettingsState extends TextualGameState {
this.htmlElement.querySelector(".category").classList.add("active");
this.htmlElement.querySelector(".categoryButton").classList.add("active");
const modsButton = this.htmlElement.querySelector(".manageMods");
if (modsButton) {
this.trackClicks(modsButton, this.onModsClicked, { preventDefault: false });
}
}
setActiveCategory(category) {
@ -196,4 +215,8 @@ export class SettingsState extends TextualGameState {
onKeybindingsClicked() {
this.moveToStateAddGoBack("KeybindingsState");
}
onModsClicked() {
this.moveToStateAddGoBack("ModsState");
}
}

View File

@ -126,15 +126,10 @@ mainMenu:
puzzleDlcWishlist: Wishlist now!
puzzleDlcViewNow: View Dlc
modsWarningPuzzleDLC: >-
Playing the Puzzle DLC is not possible with mods. Please disable all mods to play the DLC.
mods:
title: Active Mods
author: by <author>
version: v<version>
openFolder: Open Folder
folderOnlyStandalone: Opening the mod folder is only possible when running the standalone.
warningPuzzleDLC: >-
Playing the Puzzle DLC is not possible with mods. Please disable all mods to play the DLC.
puzzleMenu:
play: Play
@ -1096,6 +1091,22 @@ storyRewards:
desc: >-
You have reached the end of the demo version!
mods:
title: Mods
author: Author
version: Version
openFolder: Open Mods Folder
folderOnlyStandalone: Opening the mod folder is only possible when running the standalone.
modsInfo: >-
To install and manage mods, copy them to the mods folder within the game directory. You can also use the 'Open Mods Folder' button on the top right.
noModSupport: You need the standalone version on Steam to install mods.
togglingComingSoon:
title: Coming Soon
description: Enabling or disabling mods is currently only possible by copying the mod file from or to the mods/ folder. However, being able to toggle them here is planned for a future update!
settings:
title: Settings
categories:
@ -1111,6 +1122,7 @@ settings:
buildDate: Built <at-date>
tickrateHz: <amount> Hz
rangeSliderPercentage: <amount> %
newBadge: New!
labels:
uiScale: