diff --git a/mod_examples/add_building_basic.js b/mod_examples/add_building_basic.js index 5147613c..8b497374 100644 --- a/mod_examples/add_building_basic.js +++ b/mod_examples/add_building_basic.js @@ -1,9 +1,3 @@ -/** - * This shows how to add a new (currently useless) building. - * If you are interested in adding more logic to the game, you should also check out - * the advanced example - */ - const METADATA = { website: "https://tobspr.io", author: "tobspr", diff --git a/mod_examples/add_building_flipper.js b/mod_examples/add_building_flipper.js index 88093392..fdfcb0e2 100644 --- a/mod_examples/add_building_flipper.js +++ b/mod_examples/add_building_flipper.js @@ -1,7 +1,3 @@ -/** - * This shows how to add a new extended building (A building which flips shapes). - * Be sure to check out the "add_building_basic" example first - */ const METADATA = { website: "https://tobspr.io", author: "tobspr", diff --git a/mod_examples/base.js b/mod_examples/base.js index 94776d5e..71be904f 100644 --- a/mod_examples/base.js +++ b/mod_examples/base.js @@ -1,7 +1,3 @@ -/** - * This is the minimal structure of a mod - */ - const METADATA = { website: "https://tobspr.io", author: "tobspr", diff --git a/mod_examples/buildings_have_cost.js b/mod_examples/buildings_have_cost.js index 96f47865..92e0535e 100644 --- a/mod_examples/buildings_have_cost.js +++ b/mod_examples/buildings_have_cost.js @@ -1,7 +1,3 @@ -/** - * This shows how to patch existing methods by making belts have a cost - */ - const METADATA = { website: "https://tobspr.io", author: "tobspr", diff --git a/mod_examples/class_extensions.js b/mod_examples/class_extensions.js index 55431c32..04fa5193 100644 --- a/mod_examples/class_extensions.js +++ b/mod_examples/class_extensions.js @@ -1,7 +1,3 @@ -/** - * Shows how to extend builtin classes - */ - const METADATA = { website: "https://tobspr.io", author: "tobspr", diff --git a/mod_examples/custom_css.js b/mod_examples/custom_css.js index 5aa1219b..59a28afe 100644 --- a/mod_examples/custom_css.js +++ b/mod_examples/custom_css.js @@ -1,6 +1,3 @@ -/** - * This example shows how to add custom css - */ const METADATA = { website: "https://tobspr.io", author: "tobspr", diff --git a/mod_examples/custom_drawing.js b/mod_examples/custom_drawing.js index 23158911..60574c5f 100644 --- a/mod_examples/custom_drawing.js +++ b/mod_examples/custom_drawing.js @@ -1,7 +1,3 @@ -/** - * Shows how to draw overlays by visualizing which item processors are working - */ - const METADATA = { website: "https://tobspr.io", author: "tobspr", diff --git a/mod_examples/custom_sub_shapes.js b/mod_examples/custom_sub_shapes.js index e56a528e..47e051c1 100644 --- a/mod_examples/custom_sub_shapes.js +++ b/mod_examples/custom_sub_shapes.js @@ -1,7 +1,3 @@ -/** - * This shows how to add custom sub shapes - */ - const METADATA = { website: "https://tobspr.io", author: "tobspr", diff --git a/mod_examples/custom_theme.js b/mod_examples/custom_theme.js index 46557721..d781dd8d 100644 --- a/mod_examples/custom_theme.js +++ b/mod_examples/custom_theme.js @@ -1,6 +1,3 @@ -/** - * This example shows how to add a new theme to the game - */ const METADATA = { website: "https://tobspr.io", author: "tobspr", diff --git a/mod_examples/modify_existing_building.js b/mod_examples/modify_existing_building.js index 97ce461a..be7d122c 100644 --- a/mod_examples/modify_existing_building.js +++ b/mod_examples/modify_existing_building.js @@ -1,7 +1,3 @@ -/** - * This shows how to modify an existing building - */ - const METADATA = { website: "https://tobspr.io", author: "tobspr", diff --git a/mod_examples/modify_theme.js b/mod_examples/modify_theme.js index 8a931fd7..ca012c7f 100644 --- a/mod_examples/modify_theme.js +++ b/mod_examples/modify_theme.js @@ -1,7 +1,3 @@ -/** - * This example shows how to modify the builtin themes. If you want to create your own theme, - * be sure to check out the "custom_theme" example - */ const METADATA = { website: "https://tobspr.io", author: "tobspr", diff --git a/mod_examples/pasting.js b/mod_examples/pasting.js index 5da71fb7..ebdda2bb 100644 --- a/mod_examples/pasting.js +++ b/mod_examples/pasting.js @@ -1,7 +1,3 @@ -/** - * This is the minimal structure of a mod - */ - const METADATA = { website: "https://tobspr.io", author: "tobspr", diff --git a/mod_examples/replace_builtin_sprites.js b/mod_examples/replace_builtin_sprites.js index bed831de..009aa65a 100644 --- a/mod_examples/replace_builtin_sprites.js +++ b/mod_examples/replace_builtin_sprites.js @@ -1,8 +1,3 @@ -/** - * This example shows how to replace builtin sprites, in this case - * the color sprites - */ - const METADATA = { website: "https://tobspr.io", author: "tobspr", diff --git a/mod_examples/translations.js b/mod_examples/translations.js index b2471ba3..b0b718f2 100644 --- a/mod_examples/translations.js +++ b/mod_examples/translations.js @@ -1,7 +1,3 @@ -/** - * Shows to add new translations - */ - const METADATA = { website: "https://tobspr.io", author: "tobspr", diff --git a/mod_examples/usage_statistics.js b/mod_examples/usage_statistics.js new file mode 100644 index 00000000..a00ee2c3 --- /dev/null +++ b/mod_examples/usage_statistics.js @@ -0,0 +1,146 @@ +const METADATA = { + website: "https://tobspr.io", + author: "tobspr", + name: "Mod Example: Usage Statistics", + version: "1", + id: "usage-statistics", + description: + "Shows how to add a new component to the game, how to save additional data and how to add custom logic and drawings", +}; + +/** + * Quick info on how this mod works: + * + * It tracks how many ticks a building was idle within X seconds to compute + * the usage percentage. + * + * Every tick the logic checks if the building is idle, if so, it increases aggregatedIdleTime. + * Once X seconds are over, the aggregatedIdleTime is copied to computedUsage which + * is displayed on screen via the UsageStatisticsSystem + */ + +const MEASURE_INTERVAL_SECONDS = 5; + +class UsageStatisticsComponent extends shapez.Component { + static getId() { + return "UsageStatistics"; + } + + static getSchema() { + // Here you define which properties should be saved to the savegame + // and get automatically restored + return { + lastTimestamp: shapez.types.float, + computedUsage: shapez.types.float, + aggregatedIdleTime: shapez.types.float, + }; + } + + constructor() { + super(); + this.lastTimestamp = 0; + this.computedUsage = 0; + this.aggregatedIdleTime = 0; + } +} + +class UsageStatisticsSystem extends shapez.GameSystemWithFilter { + constructor(root) { + // By specifying the list of components, `this.allEntities` will only + // contain entities which have *all* of the specified components + super(root, [UsageStatisticsComponent, shapez.ItemProcessorComponent]); + } + + update() { + const now = this.root.time.now(); + for (let i = 0; i < this.allEntities.length; ++i) { + const entity = this.allEntities[i]; + + const processorComp = entity.components.ItemProcessor; + const usageComp = entity.components.UsageStatistics; + + if (now - usageComp.lastTimestamp > MEASURE_INTERVAL_SECONDS) { + usageComp.computedUsage = shapez.clamp( + 1 - usageComp.aggregatedIdleTime / MEASURE_INTERVAL_SECONDS + ); + usageComp.aggregatedIdleTime = 0; + usageComp.lastTimestamp = now; + } + + if (processorComp.ongoingCharges.length === 0) { + usageComp.aggregatedIdleTime += this.root.dynamicTickrate.deltaSeconds; + } + } + } + + drawChunk(parameters, chunk) { + const contents = chunk.containedEntitiesByLayer.regular; + for (let i = 0; i < contents.length; ++i) { + const entity = contents[i]; + const usageComp = entity.components.UsageStatistics; + if (!usageComp) { + continue; + } + + const staticComp = entity.components.StaticMapEntity; + const context = parameters.context; + const center = staticComp.getTileSpaceBounds().getCenter().toWorldSpace(); + + // Culling for better performance + if (parameters.visibleRect.containsCircle(center.x, center.y, 40)) { + // Background badge + context.fillStyle = "rgba(250, 250, 250, 0.8)"; + context.beginRoundedRect(center.x - 10, center.y + 3, 20, 8, 2); + + context.fill(); + + // Text + const usage = usageComp.computedUsage * 100.0; + if (usage > 99.99) { + context.fillStyle = "green"; + } else if (usage > 70) { + context.fillStyle = "orange"; + } else { + context.fillStyle = "red"; + } + + context.textAlign = "center"; + context.font = "7px GameFont"; + context.fillText(Math.round(usage) + "%", center.x, center.y + 10); + } + } + } +} + +class Mod extends shapez.Mod { + init() { + // Register the component + this.modInterface.registerComponent(UsageStatisticsComponent); + + // Add our new component to all item processor buildings so we can see how many items it processed. + // You can also inspect the entity with F8 after enabling `enableEntityInspector` to see + // the new component + const buildings = [ + shapez.MetaBalancerBuilding, + shapez.MetaCutterBuilding, + shapez.MetaRotaterBuilding, + shapez.MetaStackerBuilding, + shapez.MetaMixerBuilding, + shapez.MetaPainterBuilding, + ]; + + buildings.forEach(metaClass => { + this.modInterface.runAfterMethod(metaClass, "setupEntityComponents", function (entity) { + entity.addComponent(new UsageStatisticsComponent()); + }); + }); + + // Register our game system so we can update and draw stuff + this.modInterface.registerGameSystem({ + id: "demo_mod", + systemClass: UsageStatisticsSystem, + before: "belt", + drawHooks: ["staticAfter"], + }); + } +}