1
0
mirror of https://github.com/tobspr/shapez.io.git synced 2025-06-13 13:04:03 +00:00

Add more achievements. Add bulk achievement check signal

This commit is contained in:
Greg Considine 2021-03-07 14:26:05 -05:00
parent dc2ae5504c
commit a722c3562d
11 changed files with 214 additions and 69 deletions

View File

@ -24,30 +24,27 @@ export class AchievementProxy {
}
onLoad() {
if (this.provider.hasLoaded()) {
this.disabled = false;
return;
}
this.provider.onLoad(this.root)
.then(() => {
logger.log("Listening for unlocked achievements");
this.root.signals.achievementUnlocked.dispatch(ACHIEVEMENTS.darkMode);
logger.log("Recieving achievement signals");
this.root.signals.achievementCheck.dispatch(ACHIEVEMENTS.darkMode);
this.startSlice();
this.disabled = false;
})
.catch(err => {
this.disabled = true;
logger.error("Ignoring achievement signals", err);
})
});
}
startSlice() {
this.lastSlice = this.root.time.now();
this.root.signals.achievementUnlocked.dispatch(ACHIEVEMENTS.play1h, this.lastSlice);
this.root.signals.achievementUnlocked.dispatch(ACHIEVEMENTS.play10h, this.lastSlice);
this.root.signals.achievementUnlocked.dispatch(ACHIEVEMENTS.play20h, this.lastSlice);
this.root.signals.bulkAchievementCheck.dispatch(
ACHIEVEMENTS.play1h, this.lastSlice,
ACHIEVEMENTS.play10h, this.lastSlice,
ACHIEVEMENTS.play20h, this.lastSlice
);
}
update() {

View File

@ -149,7 +149,7 @@ export class Blueprint {
*/
tryPlace(root, tile) {
return root.logic.performBulkOperation(() => {
let anyPlaced = false;
let count = 0;
for (let i = 0; i < this.entities.length; ++i) {
const entity = this.entities[i];
if (!root.logic.checkCanPlaceEntity(entity, tile)) {
@ -161,12 +161,15 @@ export class Blueprint {
root.logic.freeEntityAreaBeforeBuild(clone);
root.map.placeStaticEntity(clone);
root.entityMgr.registerEntity(clone);
anyPlaced = true;
count++;
}
root.signals.achievementUnlocked.dispatch(ACHIEVEMENTS.placeBlueprint, anyPlaced);
root.signals.bulkAchievementCheck.dispatch(
ACHIEVEMENTS.placeBlueprint, count,
ACHIEVEMENTS.placeBp1000, count
);
return anyPlaced;
return count !== 0;
});
}
}

View File

@ -8,6 +8,7 @@ import { globalConfig } from "../../../core/config";
import { makeDiv, formatBigNumber, formatBigNumberFull } from "../../../core/utils";
import { DynamicDomAttach } from "../dynamic_dom_attach";
import { createLogger } from "../../../core/logging";
import { ACHIEVEMENTS } from "../../../platform/achievement_provider";
import { enumMouseButton } from "../../camera";
import { T } from "../../../translations";
import { KEYMAPPINGS } from "../../key_action_mapper";
@ -100,6 +101,7 @@ export class HUDMassSelector extends BaseHUDPart {
*/
const mapUidToEntity = this.root.entityMgr.getFrozenUidSearchMap();
let count = 0;
this.root.logic.performBulkOperation(() => {
for (let i = 0; i < entityUids.length; ++i) {
const uid = entityUids[i];
@ -111,8 +113,12 @@ export class HUDMassSelector extends BaseHUDPart {
if (!this.root.logic.tryDeleteBuilding(entity)) {
logger.error("Error in mass delete, could not remove building");
} else {
count++;
}
}
this.root.signals.achievementCheck.dispatch(ACHIEVEMENTS.destroy1000, count);
});
// Clear uids later

View File

@ -15,6 +15,7 @@ import {
removeAllChildren,
} from "../../../core/utils";
import { Vector } from "../../../core/vector";
import { ACHIEVEMENTS } from "../../../platform/achievement_provider";
import { T } from "../../../translations";
import { BaseItem } from "../../base_item";
import { MetaHubBuilding } from "../../buildings/hub";
@ -349,6 +350,10 @@ export class HUDWaypoints extends BaseHUDPart {
T.ingame.waypoints.creationSuccessNotification,
enumNotificationType.success
);
this.root.signals.achievementCheck.dispatch(
ACHIEVEMENTS.mapMarkers15,
this.waypoints.length - 1 // Disregard HUB
);
// Re-render the list and thus add it
this.rerenderWaypointList();

View File

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

View File

@ -97,7 +97,7 @@ export class ShapeDefinitionManager extends BasicSerializableObject {
const rightSide = definition.cloneFilteredByQuadrants([2, 3]);
const leftSide = definition.cloneFilteredByQuadrants([0, 1]);
this.root.signals.achievementUnlocked.dispatch(ACHIEVEMENTS.cutShape);
this.root.signals.achievementCheck.dispatch(ACHIEVEMENTS.cutShape);
return /** @type {[ShapeDefinition, ShapeDefinition]} */ (this.operationCache[key] = [
this.registerOrReturnHandle(rightSide),
@ -140,7 +140,7 @@ export class ShapeDefinitionManager extends BasicSerializableObject {
const rotated = definition.cloneRotateCW();
this.root.signals.achievementUnlocked.dispatch(ACHIEVEMENTS.rotateShape);
this.root.signals.achievementCheck.dispatch(ACHIEVEMENTS.rotateShape);
return /** @type {ShapeDefinition} */ (this.operationCache[key] = this.registerOrReturnHandle(
rotated
@ -195,7 +195,7 @@ export class ShapeDefinitionManager extends BasicSerializableObject {
return /** @type {ShapeDefinition} */ (this.operationCache[key]);
}
this.root.signals.achievementUnlocked.dispatch(ACHIEVEMENTS.stackShape);
this.root.signals.achievementCheck.dispatch(ACHIEVEMENTS.stackShape);
const stacked = lowerDefinition.cloneAndStackWith(upperDefinition);
return /** @type {ShapeDefinition} */ (this.operationCache[key] = this.registerOrReturnHandle(
@ -215,7 +215,7 @@ export class ShapeDefinitionManager extends BasicSerializableObject {
return /** @type {ShapeDefinition} */ (this.operationCache[key]);
}
this.root.signals.achievementUnlocked.dispatch(ACHIEVEMENTS.paintShape);
this.root.signals.achievementCheck.dispatch(ACHIEVEMENTS.paintShape);
const colorized = definition.cloneAndPaintWith(color);
return /** @type {ShapeDefinition} */ (this.operationCache[key] = this.registerOrReturnHandle(
@ -252,11 +252,14 @@ export class ShapeDefinitionManager extends BasicSerializableObject {
}
this.shapeKeyToDefinition[id] = definition;
this.root.signals.achievementUnlocked.dispatch(ACHIEVEMENTS.oldLevel17, definition);
this.root.signals.achievementUnlocked.dispatch(ACHIEVEMENTS.produceLogo, definition);
this.root.signals.achievementUnlocked.dispatch(ACHIEVEMENTS.produceMsLogo, definition);
this.root.signals.achievementUnlocked.dispatch(ACHIEVEMENTS.produceRocket, definition);
this.root.signals.achievementUnlocked.dispatch(ACHIEVEMENTS.stack4Layers, definition);
this.root.signals.bulkAchievementCheck.dispatch(
ACHIEVEMENTS.logoBefore18, definition,
ACHIEVEMENTS.oldLevel17, definition,
ACHIEVEMENTS.produceLogo, definition,
ACHIEVEMENTS.produceMsLogo, definition,
ACHIEVEMENTS.produceRocket, definition,
ACHIEVEMENTS.stack4Layers, definition
);
// logger.log("Registered shape with key (2)", id);
return definition;

View File

@ -276,7 +276,7 @@ export class ItemEjectorSystem extends GameSystemWithFilter {
if (storageComp.canAcceptItem(item)) {
storageComp.takeItem(item);
this.root.signals.achievementUnlocked.dispatch(ACHIEVEMENTS.storeShape, storageComp);
this.root.signals.achievementCheck.dispatch(ACHIEVEMENTS.storeShape, storageComp);
return true;
}

View File

@ -698,7 +698,7 @@ export class WireSystem extends GameSystemWithFilter {
return;
}
this.root.signals.achievementUnlocked.dispatch(ACHIEVEMENTS.place5000Wires, entity);
this.root.signals.achievementCheck.dispatch(ACHIEVEMENTS.place5000Wires, entity);
// Invalidate affected area
const originalRect = staticComp.getTileSpaceBounds();

View File

@ -13,14 +13,18 @@ export const ACHIEVEMENTS = {
completeLvl26: "completeLvl26",
cutShape: "cutShape",
darkMode: "darkMode",
destroy1000: "destroy1000",
irrelevantShape: "irrelevantShape",
level100: "level100",
level50: "level50",
logoBefore18: "logoBefore18",
mapMarkers15: "mapMarkers15",
oldLevel17: "oldLevel17",
openWires: "openWires",
paintShape: "paintShape",
place5000Wires: "place5000Wires",
placeBlueprint: "placeBlueprint",
placeBp1000: "placeBp1000",
play1h: "play1h",
play10h: "play10h",
play20h: "play20h",
@ -33,6 +37,8 @@ export const ACHIEVEMENTS = {
store100Unique: "store100Unique",
storeShape: "storeShape",
unlockWires: "unlockWires",
upgradesTier5: "upgradesTier5",
upgradesTier8: "upgradesTier8",
};
const DARK_MODE = "dark";
@ -149,14 +155,23 @@ export class AchievementCollection {
this.createAndSet(ACHIEVEMENTS.darkMode, {
isValid: this.isDarkModeValid,
});
/*
*this.createAndSet(ACHIEVEMENTS.irrelevantShape, {
* isValid: this.isIrrelevantShapeValid,
* signal: "shapeDelivered",
*});
*/
this.createAndSet(ACHIEVEMENTS.destroy1000, {
isValid: this.isDestroy1000Valid,
});
this.createAndSet(ACHIEVEMENTS.irrelevantShape, {
isValid: this.isIrrelevantShapeValid,
signal: "shapeDelivered",
});
this.createAndSet(ACHIEVEMENTS.level100, this.createLevelOptions(100));
this.createAndSet(ACHIEVEMENTS.level50, this.createLevelOptions(50));
this.createAndSet(ACHIEVEMENTS.logoBefore18, {
isRelevant: this.isLogoBefore18Relevant,
isValid: this.isLogoBefore18Valid,
});
this.createAndSet(ACHIEVEMENTS.mapMarkers15, {
isRelevant: this.isMapMarkers15Relevant,
isValid: this.isMapMarkers15Valid,
});
this.createAndSet(ACHIEVEMENTS.oldLevel17, this.createShapeOptions(SHAPE_OLD_LEVEL_17));
this.createAndSet(ACHIEVEMENTS.openWires, {
isValid: this.isOpenWiresValid,
@ -169,6 +184,9 @@ export class AchievementCollection {
this.createAndSet(ACHIEVEMENTS.placeBlueprint, {
isValid: this.isPlaceBlueprintValid,
});
this.createAndSet(ACHIEVEMENTS.placeBp1000, {
isValid: this.isPlaceBp1000Valid,
});
this.createAndSet(ACHIEVEMENTS.play1h, this.createTimeOptions(HOUR_1));
this.createAndSet(ACHIEVEMENTS.play10h, this.createTimeOptions(HOUR_10));
this.createAndSet(ACHIEVEMENTS.play20h, this.createTimeOptions(HOUR_20));
@ -190,12 +208,15 @@ export class AchievementCollection {
signal: "entityGotNewComponent",
});
this.createAndSet(ACHIEVEMENTS.unlockWires, this.createLevelOptions(20));
this.createAndSet(ACHIEVEMENTS.upgradesTier5, this.createUpgradeOptions(5));
this.createAndSet(ACHIEVEMENTS.upgradesTier8, this.createUpgradeOptions(8));
}
/** @param {GameRoot} root */
initialize(root) {
this.root = root;
this.root.signals.achievementUnlocked.add(this.unlock, this);
this.root.signals.achievementCheck.add(this.unlock, this);
this.root.signals.bulkAchievementCheck.add(this.bulkUnlock, this);
for (let [key, achievement] of this.map.entries()) {
if (!achievement.isRelevant()) {
@ -210,7 +231,7 @@ export class AchievementCollection {
}
if (!this.hasDefaultReceivers()) {
this.root.signals.achievementUnlocked.remove(this.unlock);
this.root.signals.achievementCheck.remove(this.unlock);
}
}
@ -222,6 +243,10 @@ export class AchievementCollection {
* @param {string} [options.signal]
*/
createAndSet(key, options = {}) {
if (G_IS_DEV) {
assert(ACHIEVEMENTS[key], "Achievement key not found: ", key);
}
const achievement = new Achievement(key);
achievement.activate = this.activate;
@ -241,6 +266,12 @@ export class AchievementCollection {
this.map.set(key, achievement);
}
bulkUnlock() {
for (let i = 0; i < arguments.length; i += 2) {
this.unlock(arguments[i], arguments[i + 1]);
}
}
/**
* @param {string} key - Maps to an Achievement
* @param {*[]} [arguments] - Additional arguments received from signal dispatches
@ -271,11 +302,11 @@ export class AchievementCollection {
* @param {?Error} err - Error is null if activation was successful
* @param {string} key - Maps to an Achievement
*/
onActivate (err, key) {
onActivate(err, key) {
this.remove(key);
if (!this.hasDefaultReceivers()) {
this.root.signals.achievementUnlocked.remove(this.unlock);
this.root.signals.achievementCheck.remove(this.unlock);
}
}
@ -304,25 +335,45 @@ export class AchievementCollection {
return false;
}
createLevelOptions (level) {
hasAllUpgradesAtTier(tier) {
const upgrades = this.root.gameMode.getUpgrades();
for (let upgradeId in upgrades) {
if (this.root.hubGoals.getUpgradeLevel(upgradeId) < tier - 1) {
return false;
}
}
return true;
}
createLevelOptions(level) {
return {
isRelevant: () => this.root.hubGoals.level < level,
isValid: (key, currentLevel) => currentLevel === level,
signal: "storyGoalCompleted"
}
signal: "storyGoalCompleted",
};
}
createShapeOptions (shape) {
createShapeOptions(shape) {
return {
isValid: (key, definition) => definition.cachedHash === shape
}
isValid: (key, definition) => definition.cachedHash === shape,
};
}
createTimeOptions (duration) {
createTimeOptions(duration) {
return {
isRelevant: () => this.root.time.now() < duration,
isValid: () => this.root.time.now() >= duration,
}
};
}
createUpgradeOptions(tier) {
return {
isRelevant: () => !this.hasAllUpgradesAtTier(tier),
isValid: () => this.hasAllUpgradesAtTier(tier),
signal: "upgradePurchased",
};
}
/**
@ -340,8 +391,10 @@ export class AchievementCollection {
* @returns {boolean}
*/
isBlueprint100kValid(key, definition) {
return definition.cachedHash === SHAPE_BLUEPRINT &&
this.root.hubGoals.storedShapes[SHAPE_BLUEPRINT] >= 100000;
return (
definition.cachedHash === SHAPE_BLUEPRINT &&
this.root.hubGoals.storedShapes[SHAPE_BLUEPRINT] >= 100000
);
}
/**
@ -350,8 +403,10 @@ export class AchievementCollection {
* @returns {boolean}
*/
isBlueprint1mValid(key, definition) {
return definition.cachedHash === SHAPE_BLUEPRINT &&
this.root.hubGoals.storedShapes[SHAPE_BLUEPRINT] >= 1000000;
return (
definition.cachedHash === SHAPE_BLUEPRINT &&
this.root.hubGoals.storedShapes[SHAPE_BLUEPRINT] >= 1000000
);
}
/**
@ -362,13 +417,66 @@ export class AchievementCollection {
return this.root.app.settings.currentData.settings.theme === DARK_MODE;
}
/**
* @param {string} key
* @param {number} count - The count of selected entities destroyed
* @returns {boolean}
*/
isDestroy1000Valid(key, count) {
return count >= 1000;
}
/**
* @param {string} key
* @param {ShapeDefinition} definition
* @returns {boolean}
*/
isIrrelevantShapeValid(key, definition) {
//return definition.cachedHash !== this.hubGoals.currentGoal.definition.cachedHash
if (definition.cachedHash === this.root.hubGoals.currentGoal.definition.cachedHash) {
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;
}
}
}
return true;
}
/** @returns {boolean} */
isLogoBefore18Relevant() {
return this.root.hubGoals.level < 18;
}
/**
* @param {string} key
* @param {ShapeDefinition} definition
* @returns {boolean}
*/
isLogoBefore18Valid(key, definition) {
return this.root.hubGoals.level < 18 && definition.cachedHash === SHAPE_LOGO;
}
/** @returns {boolean} */
isMapMarkers15Relevant() {
return this.root.hud.parts.waypoints.waypoints.length < 16; // 16 - HUB
}
/**
* @param {string} key
* @param {number} count - Count of map markers excluding HUB
* @returns {boolean}
*/
isMapMarkers15Valid(key, count) {
return count === 15;
}
/**
@ -386,18 +494,29 @@ export class AchievementCollection {
* @returns {boolean}
*/
isPlace5000WiresValid(key, entity) {
return entity.components.Wire &&
return (
entity.components.Wire &&
entity.registered &&
entity.root.entityMgr.componentToEntity.Wire.length === 5000;
entity.root.entityMgr.componentToEntity.Wire.length === 5000
);
}
/**
* @param {string} key
* @param {boolean} anyPlaced
* @param {number} count
* @returns {boolean}
*/
isPlaceBlueprintValid(key, anyPlaced) {
return anyPlaced;
isPlaceBlueprintValid(key, count) {
return count != 0;
}
/**
* @param {string} key
* @param {number} count
* @returns {boolean}
*/
isPlaceBp1000Valid(key, count) {
return count >= 1000;
}
/**

View File

@ -14,7 +14,7 @@ export class NoAchievementProvider extends AchievementProviderInterface {
}
onLoad() {
return Promise.resolve();
return Promise.reject(new Error("No achievements to load"));
}
activate() {

View File

@ -16,14 +16,18 @@ const ACHIEVEMENT_IDS = {
[ACHIEVEMENTS.completeLvl26]: "complete_lvl_26",
[ACHIEVEMENTS.cutShape]: "cut_shape",
[ACHIEVEMENTS.darkMode]: "dark_mode",
[ACHIEVEMENTS.destroy1000]: "destroy_1000",
[ACHIEVEMENTS.irrelevantShape]: "irrelevant_shape",
[ACHIEVEMENTS.level100]: "level_100",
[ACHIEVEMENTS.level50]: "level_50",
[ACHIEVEMENTS.logoBefore18]: "logo_before_18",
[ACHIEVEMENTS.mapMarkers15]: "map_markers_15",
[ACHIEVEMENTS.openWires]: "open_wires",
[ACHIEVEMENTS.oldLevel17]: "old_level_17",
[ACHIEVEMENTS.paintShape]: "paint_shape",
[ACHIEVEMENTS.place5000Wires]: "place_5000_wires",
[ACHIEVEMENTS.placeBlueprint]: "place_blueprint",
[ACHIEVEMENTS.placeBp1000]: "place_bp_1000",
[ACHIEVEMENTS.play1h]: "play_1h",
[ACHIEVEMENTS.play10h]: "play_10h",
[ACHIEVEMENTS.play20h]: "play_20h",
@ -36,6 +40,8 @@ const ACHIEVEMENT_IDS = {
[ACHIEVEMENTS.store100Unique]: "store_100_unique",
[ACHIEVEMENTS.storeShape]: "store_shape",
[ACHIEVEMENTS.unlockWires]: "unlock_wires",
[ACHIEVEMENTS.upgradesTier5]: "upgrades_tier_5",
[ACHIEVEMENTS.upgradesTier8]: "upgrades_tier_8",
};
export class SteamAchievementProvider extends AchievementProviderInterface {
@ -44,9 +50,15 @@ export class SteamAchievementProvider extends AchievementProviderInterface {
super(app);
this.initialized = false;
this.loaded = false;
this.saveId = null;
this.collection = new AchievementCollection(this.activate.bind(this));
if (G_IS_DEV) {
for (let key in ACHIEVEMENT_IDS) {
assert(this.collection.map.has(key), "Key not found in collection: " + key);
}
}
logger.log("Collection created with", this.collection.map.size, "achievements");
}
@ -55,27 +67,26 @@ export class SteamAchievementProvider extends AchievementProviderInterface {
return true;
}
/** @returns {boolean} */
hasLoaded() {
return this.loaded;
}
/**
* @param {GameRoot} root
* @returns {Promise<void>}
*/
onLoad(root) {
if (this.loaded) {
return Promise.resolve();
}
this.root = root;
try {
this.collection.initialize(root);
this.loaded = true;
logger.log(this.collection.map.size, "achievements are relevant and initialized");
if (!this.saveId || this.saveId === this.root.savegame.internalId) {
this.collection.initialize(root);
} else {
this.collection = new AchievementCollection(this.activate.bind(this));
this.collection.initialize(root);
}
logger.log("Initialized", this.collection.map.size, "relevant achievements");
this.saveId = this.root.savegame.internalId;
return Promise.resolve();
} catch (err) {
logger.error("Failed to initialize the achievement collection");
logger.error("Failed to initialize the collection");
return Promise.reject(err);
}
}