1
0
mirror of https://github.com/tobspr/shapez.io.git synced 2026-03-02 03:39:21 +00:00

Prepare the achievements update

This commit is contained in:
Tobias Springer
2021-03-10 09:29:20 +01:00
parent 837b0d8007
commit 226149a40f
21 changed files with 11916 additions and 12405 deletions

View File

@@ -1,10 +1,13 @@
export const CHANGELOG = [
{
version: "1.2.3",
date: "unreleased",
version: "1.3.0",
date: "12.03.2020",
skin: "achievements",
entries: [
"There are now <strong>45 Steam Achievements!</strong>",
"Fixed constant signals being editable from the regular layer",
"Fixed items still overlapping sometimes between buildings and belts",
"Updated translations (Thanks to all contributors!)",
],
},
{

View File

@@ -12,8 +12,6 @@ const logger = createLogger("achievement_proxy");
const ROTATER = "rotater";
const DEFAULT = "default";
const BELT = "belt";
const LEVEL_26 = 26;
export class AchievementProxy {
/** @param {GameRoot} root */
@@ -22,7 +20,9 @@ export class AchievementProxy {
this.provider = this.root.app.achievementProvider;
this.disabled = true;
if (!this.provider.hasAchievements()) {
if (G_IS_DEV && globalConfig.debug.testAchievements) {
// still enable the proxy
} else if (!this.provider.hasAchievements()) {
return;
}
@@ -34,7 +34,8 @@ export class AchievementProxy {
}
onLoad() {
this.provider.onLoad(this.root)
this.provider
.onLoad(this.root)
.then(() => {
this.disabled = false;
logger.log("Recieving achievement signals");
@@ -50,6 +51,8 @@ export class AchievementProxy {
this.root.signals.achievementCheck.dispatch(ACHIEVEMENTS.darkMode);
if (this.has(ACHIEVEMENTS.mam)) {
this.root.signals.entityAdded.add(this.onMamFailure, this);
this.root.signals.entityDestroyed.add(this.onMamFailure, this);
this.root.signals.storyGoalCompleted.add(this.onStoryGoalCompleted, this);
}
@@ -57,10 +60,6 @@ export class AchievementProxy {
this.root.signals.entityAdded.add(this.onEntityAdded, this);
}
if (this.has(ACHIEVEMENTS.noBeltUpgradesUntilBp)) {
this.root.signals.upgradePurchased.add(this.onUpgradePurchased, this);
}
this.startSlice();
}
@@ -73,27 +72,38 @@ export class AchievementProxy {
// Every other slice
if (this.sliceIteration % 2 === 0) {
this.root.signals.bulkAchievementCheck.dispatch(
ACHIEVEMENTS.throughputBp25, this.sliceTime,
ACHIEVEMENTS.throughputBp50, this.sliceTime,
ACHIEVEMENTS.throughputLogo25, this.sliceTime,
ACHIEVEMENTS.throughputLogo50, this.sliceTime,
ACHIEVEMENTS.throughputRocket10, this.sliceTime,
ACHIEVEMENTS.throughputRocket20, this.sliceTime
ACHIEVEMENTS.throughputBp25,
this.sliceTime,
ACHIEVEMENTS.throughputBp50,
this.sliceTime,
ACHIEVEMENTS.throughputLogo25,
this.sliceTime,
ACHIEVEMENTS.throughputLogo50,
this.sliceTime,
ACHIEVEMENTS.throughputRocket10,
this.sliceTime,
ACHIEVEMENTS.throughputRocket20,
this.sliceTime
);
}
// Every 3rd slice
if (this.sliceIteration % 3 === 0) {
this.root.signals.bulkAchievementCheck.dispatch(
ACHIEVEMENTS.play1h, this.sliceTime,
ACHIEVEMENTS.play10h, this.sliceTime,
ACHIEVEMENTS.play20h, this.sliceTime
ACHIEVEMENTS.play1h,
this.sliceTime,
ACHIEVEMENTS.play10h,
this.sliceTime,
ACHIEVEMENTS.play20h,
this.sliceTime
);
}
// Every 10th slice
if (this.sliceIteration % 10 === 0) {
this.provider.collection.clean();
if (this.provider.collection) {
this.provider.collection.clean();
}
}
if (this.sliceIteration === this.sliceIterationLimit) {
@@ -118,6 +128,9 @@ export class AchievementProxy {
* @returns {boolean}
*/
has(key) {
if (!this.provider.collection) {
return false;
}
return this.provider.collection.map.has(key);
}
@@ -127,7 +140,7 @@ export class AchievementProxy {
return;
}
const building = getBuildingDataFromCode(entity.components.StaticMapEntity.code)
const building = getBuildingDataFromCode(entity.components.StaticMapEntity.code);
if (building.metaInstance.id !== ROTATER) {
return;
@@ -143,28 +156,18 @@ export class AchievementProxy {
/** @param {number} level */
onStoryGoalCompleted(level) {
if (level === LEVEL_26) {
if (level > 26) {
this.root.signals.entityAdded.add(this.onMamFailure, this);
this.root.signals.entityDestroyed.add(this.onMamFailure, this);
} else if (level === LEVEL_26 + 1) {
this.root.signals.storyGoalCompleted.remove(this.onStoryGoalCompleted, this);
}
this.root.signals.achievementCheck.dispatch(ACHIEVEMENTS.mam);
// reset on every level
this.root.savegame.currentData.stats.failedMam = false;
}
onMamFailure() {
this.root.savegame.currentData.stats.failedMam = true;
this.root.signals.entityAdded.remove(this.onMamFailure);
this.root.signals.entityDestroyed.remove(this.onMamFailure);
this.root.signals.storyGoalCompleted.remove(this.onStoryGoalCompleted);
}
/** @param {string} upgrade */
onUpgradePurchased(upgrade) {
if (upgrade !== BELT) {
return;
}
this.root.savegame.currentData.stats.upgradedBelt = true;
this.root.signals.upgradePurchased.remove(this.onUpgradePurchased);
}
}

View File

@@ -182,7 +182,7 @@ export class GameRoot {
// Called with an achievement key and necessary args to validate it can be unlocked.
achievementCheck: /** @type {TypedSignal<[string, *]>} */ (new Signal()),
bulkAchievementCheck: /** @type {TypedSignal<[string, any]...>} */ (new Signal()),
bulkAchievementCheck: /** @type {TypedSignal<(string|any)[]>} */ (new Signal()),
};
// RNG's

View File

@@ -1,12 +1,14 @@
/* typehints:start */
import { Application } from "../application";
import { StorageComponent } from "../game/components/storage";
import { ShapeItem } from "../game/items/shape_item";
import { Entity } from "../game/entity";
import { GameRoot } from "../game/root";
import { ShapeDefinition } from "../game/shape_definition";
import { THEMES } from "../game/theme";
/* typehints:end */
import { enumAnalyticsDataSource } from "../game/production_analytics";
import { ShapeItem } from "../game/items/shape_item";
export const ACHIEVEMENTS = {
belt500Tiles: "belt500Tiles",
blueprint100k: "blueprint100k",
@@ -55,16 +57,16 @@ export const ACHIEVEMENTS = {
upgradesTier8: "upgradesTier8",
};
/** @type {keyof typeof THEMES} */
const DARK_MODE = "dark";
const HOUR_1 = 3600; // Seconds
const HOUR_10 = HOUR_1 * 10;
const HOUR_20 = HOUR_1 * 20;
const ITEM_SHAPE = "shape";
const ITEM_SHAPE = ShapeItem.getId();
const MINUTE_30 = 1800; // Seconds
const MINUTE_60 = MINUTE_30 * 2;
const MINUTE_120 = MINUTE_30 * 4;
const PRODUCED = "produced";
const RATE_SLICE_COUNT = 10;
const ROTATER_CCW_CODE = 12;
const ROTATER_180_CODE = 13;
const SHAPE_BP = "CbCbCbRb:CwCwCwCw";
@@ -72,9 +74,15 @@ const SHAPE_LOGO = "RuCw--Cw:----Ru--";
const SHAPE_MS_LOGO = "RgRyRbRr";
const SHAPE_OLD_LEVEL_17 = "WrRgWrRg:CwCrCwCr:SgSgSgSg";
const SHAPE_ROCKET = "CbCuCbCu:Sr------:--CrSrCr:CwCwCwCw";
/** @type {Layer} */
const WIRE_LAYER = "wires";
export class AchievementProviderInterface {
/* typehints:start */
collection = /** @type {AchievementCollection|undefined} */ (null);
/* typehints:end */
/** @param {Application} app */
constructor(app) {
this.app = app;
@@ -135,9 +143,7 @@ export class Achievement {
this.signal = null;
}
init() {
}
init() {}
isValid() {
return true;
@@ -193,19 +199,16 @@ export class AchievementCollection {
this.add(ACHIEVEMENTS.logoBefore18, {
isRelevant: this.isLogoBefore18Relevant,
isValid: this.isLogoBefore18Valid,
signal: "itemProduced"
signal: "itemProduced",
});
this.add(ACHIEVEMENTS.mam, {
isRelevant: this.isMamRelevant,
isValid: this.isMamValid,
signal: "storyGoalCompleted",
});
this.add(ACHIEVEMENTS.mapMarkers15, {
isRelevant: this.isMapMarkers15Relevant,
isValid: this.isMapMarkers15Valid,
});
this.add(ACHIEVEMENTS.noBeltUpgradesUntilBp, {
init: this.initNoBeltUpgradesUntilBp,
isRelevant: this.isNoBeltUpgradesUntilBpRelevant,
isValid: this.isNoBeltUpgradesUntilBpValid,
signal: "storyGoalCompleted",
@@ -354,7 +357,8 @@ export class AchievementCollection {
return;
}
achievement.unlock()
achievement
.unlock()
.then(() => {
this.onActivate(null, key);
})
@@ -380,12 +384,13 @@ export class AchievementCollection {
/** @param {string} key - Maps to an Achievement */
remove(key) {
const achievement = this.map.get(key);
if (achievement) {
if (achievement.receiver) {
this.root.signals[achievement.signal].remove(achievement.receiver);
}
if (achievement.receiver) {
this.root.signals[achievement.signal].remove(achievement.receiver);
this.map.delete(key);
}
this.map.delete(key);
}
/**
@@ -447,7 +452,7 @@ export class AchievementCollection {
createLevelOptions(level) {
return {
isRelevant: () => this.root.hubGoals.level < level,
isValid: (currentLevel) => currentLevel === level,
isValid: currentLevel => currentLevel === level,
signal: "storyGoalCompleted",
};
}
@@ -455,17 +460,19 @@ export class AchievementCollection {
createRateOptions(shape, rate) {
return {
isValid: () => {
return this.root.productionAnalytics.getCurrentShapeRate(
PRODUCED,
this.root.shapeDefinitionMgr.getShapeFromShortKey(shape)
) >= rate;
}
return (
this.root.productionAnalytics.getCurrentShapeRate(
enumAnalyticsDataSource.delivered,
this.root.shapeDefinitionMgr.getShapeFromShortKey(shape)
) >= rate
);
},
};
}
createShapeOptions(shape) {
return {
isValid: (item) => this.isShape(item, shape),
isValid: item => this.isShape(item, shape),
signal: "itemProduced",
};
}
@@ -473,7 +480,7 @@ export class AchievementCollection {
createSpeedOptions(level, time) {
return {
isRelevant: () => this.root.hubGoals.level <= level && this.root.time.now() < time,
isValid: (currentLevel) => currentLevel === level && this.root.time.now() < time,
isValid: currentLevel => currentLevel === level && this.root.time.now() < time,
signal: "storyGoalCompleted",
};
}
@@ -500,18 +507,12 @@ export class AchievementCollection {
/** @param {ShapeDefinition} definition @returns {boolean} */
isBlueprint100kValid(definition) {
return (
definition.cachedHash === SHAPE_BP &&
this.root.hubGoals.storedShapes[SHAPE_BP] >= 100000
);
return definition.cachedHash === SHAPE_BP && this.root.hubGoals.storedShapes[SHAPE_BP] >= 100000;
}
/** @param {ShapeDefinition} definition @returns {boolean} */
isBlueprint1mValid(definition) {
return (
definition.cachedHash === SHAPE_BP &&
this.root.hubGoals.storedShapes[SHAPE_BP] >= 1000000
);
return definition.cachedHash === SHAPE_BP && this.root.hubGoals.storedShapes[SHAPE_BP] >= 1000000;
}
/** @returns {boolean} */
@@ -530,14 +531,18 @@ export class AchievementCollection {
return false;
}
if (definition.cachedHash === this.root.gameMode.getBlueprintShapeKey()) {
return false;
}
const upgrades = this.root.gameMode.getUpgrades();
for (let upgradeId in upgrades) {
const currentTier = this.root.hubGoals.getUpgradeLevel(upgradeId);
const requiredShapes = upgrades[upgradeId][currentTier].required;
for (let i = 0; i < requiredShapes.length; i++) {
if (definition.cachedHash === requiredShapes[i].shape) {
return false;
for (const tier in upgrades[upgradeId]) {
const requiredShapes = upgrades[upgradeId][tier].required;
for (let i = 0; i < requiredShapes.length; i++) {
if (definition.cachedHash === requiredShapes[i].shape) {
return false;
}
}
}
}
@@ -555,28 +560,9 @@ export class AchievementCollection {
return this.root.hubGoals.level < 18 && this.isShape(item, SHAPE_LOGO);
}
initMam() {
const stats = this.root.savegame.currentData.stats;
if (stats.failedMam === true) {
return;
}
if (this.root.hubGoals.level === 26 && stats.failedMam === false) {
return;
}
stats.failedMam = this.root.hubGoals.level < 26;
}
/** @returns {boolean} */
isMamRelevant() {
return this.root.hubGoals.level <= 26 && !this.root.savegame.currentData.stats.failedMam;
}
/** @params {number} level @returns {boolean} */
isMamValid(level) {
return level === 27 && !this.root.savegame.currentData.stats.failedMam;
isMamValid() {
return this.root.hubGoals.level > 27 && !this.root.savegame.currentData.stats.failedMam;
}
/** @returns {boolean} */
@@ -589,16 +575,6 @@ export class AchievementCollection {
return count === 15;
}
initNoBeltUpgradesUntilBp() {
const stats = this.root.savegame.currentData.stats;
if (stats.upgradedBelt === true) {
return;
}
stats.upgradedBelt = this.root.hubGoals.upgradeLevels.belt > 0;
}
/** @returns {boolean} */
isNoBeltUpgradesUntilBpRelevant() {
return this.root.hubGoals.level <= 12 && this.root.hubGoals.upgradeLevels.belt === 0;
@@ -631,8 +607,7 @@ export class AchievementCollection {
/** @returns {boolean} */
isNoInverseRotaterRelevant() {
return this.root.hubGoals.level < 14 &&
!this.root.savegame.currentData.stats.usedInverseRotater;
return this.root.hubGoals.level < 14 && !this.root.savegame.currentData.stats.usedInverseRotater;
}
/** @param {number} level @returns {boolean} */

View File

@@ -203,12 +203,11 @@ export class PlatformWrapperImplBrowser extends PlatformWrapperInterface {
if (G_IS_DEV && globalConfig.debug.testAchievements) {
this.app.achievementProvider = new SteamAchievementProvider(this.app);
return this.app.achievementProvider.initialize()
.catch(err => {
logger.error("Failed to initialize achievement provider, disabling:", err);
return this.app.achievementProvider.initialize().catch(err => {
logger.error("Failed to initialize achievement provider, disabling:", err);
this.app.achievementProvider = new NoAchievementProvider(this.app);
});
this.app.achievementProvider = new NoAchievementProvider(this.app);
});
}
return this.app.achievementProvider.initialize();

View File

@@ -78,7 +78,11 @@ export class Savegame extends ReadWriteProxy {
return {
version: this.getCurrentVersion(),
dump: null,
stats: {},
stats: {
failedMam: false,
trashedCount: 0,
usedInverseRotater: false,
},
lastUpdate: Date.now(),
};
}

View File

@@ -24,9 +24,9 @@ export class SavegameInterface_V1008 extends SavegameInterface_V1007 {
}
Object.assign(data.stats, {
failedMam: false,
failedMam: true,
trashedCount: 0,
usedInverseRotater: false
usedInverseRotater: true,
});
}
}

View File

@@ -19,7 +19,7 @@ export class ChangelogState extends TextualGameState {
for (let i = 0; i < entries.length; ++i) {
const entry = entries[i];
html += `
<div class="entry">
<div class="entry" data-changelog-skin="${entry.skin || "default"}">
<span class="version">${entry.version}</span>
<span class="date">${entry.date}</span>
<ul class="changes">

View File

@@ -66,7 +66,7 @@ export class MainMenuState extends GameState {
<img src="${cachebust(
G_CHINA_VERSION ? "res/logo_cn.png" : "res/logo.png"
)}" alt="shapez.io Logo">
<span class="updateLabel">v${G_BUILD_VERSION}</span>
<span class="updateLabel">v${G_BUILD_VERSION} - Achievements!</span>
</div>
<div class="mainWrapper ${showDemoBadges ? "demo" : "noDemo"}">

View File

@@ -199,7 +199,9 @@ export class PreloadState extends GameState {
for (let i = 0; i < changelogEntries.length; ++i) {
const entry = changelogEntries[i];
dialogHtml += `
<div class="changelogDialogEntry">
<div class="changelogDialogEntry" data-changelog-skin="${
entry.skin || "default"
}">
<span class="version">${entry.version}</span>
<span class="date">${entry.date}</span>
<ul class="changes">