{"id":25743042,"url":"https://github.com/ddmills/geotic","last_synced_at":"2025-10-09T01:32:27.705Z","repository":{"id":9836815,"uuid":"63456704","full_name":"ddmills/geotic","owner":"ddmills","description":"Entity Component System library for javascript","archived":false,"fork":false,"pushed_at":"2025-03-05T18:50:34.000Z","size":1041,"stargazers_count":189,"open_issues_count":11,"forks_count":15,"subscribers_count":8,"default_branch":"master","last_synced_at":"2025-09-18T13:35:32.523Z","etag":null,"topics":["component","ecs","engine","entity","es6","event","fast","game","geotic","javascript","library","npm","pattern","performance","prefab","query","rogue","roguelike","serialize","system"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ddmills.png","metadata":{"files":{"readme":"readme.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2016-07-15T23:39:36.000Z","updated_at":"2025-09-02T06:03:53.000Z","dependencies_parsed_at":"2024-06-19T01:48:13.228Z","dependency_job_id":"89b311d9-c540-4255-8c98-f0a8d01fc1b1","html_url":"https://github.com/ddmills/geotic","commit_stats":{"total_commits":195,"total_committers":7,"mean_commits":"27.857142857142858","dds":"0.32307692307692304","last_synced_commit":"02e52c46f9f42657cc623b114964f8740fd9c80f"},"previous_names":[],"tags_count":3,"template":false,"template_full_name":null,"purl":"pkg:github/ddmills/geotic","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ddmills%2Fgeotic","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ddmills%2Fgeotic/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ddmills%2Fgeotic/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ddmills%2Fgeotic/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ddmills","download_url":"https://codeload.github.com/ddmills/geotic/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ddmills%2Fgeotic/sbom","scorecard":{"id":330277,"data":{"date":"2025-08-11","repo":{"name":"github.com/ddmills/geotic","commit":"e061678b3a179c0dba3ca3f1ad3abc25570328c7"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":1.7,"checks":[{"name":"Code-Review","score":0,"reason":"Found 1/26 approved changesets -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"Token-Permissions","score":-1,"reason":"No tokens found","details":null,"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"Dangerous-Workflow","score":-1,"reason":"no workflows found","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Maintained","score":0,"reason":"0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Pinned-Dependencies","score":-1,"reason":"no dependencies found","details":null,"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE:0","Info: FSF or OSI recognized license: MIT License: LICENSE:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Branch-Protection","score":0,"reason":"branch protection not enabled on development/release branches","details":["Warn: branch protection not enabled for branch 'master'"],"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"SAST","score":0,"reason":"SAST tool is not run on all commits -- score normalized to 0","details":["Warn: 0 commits out of 8 are checked with a SAST tool"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}},{"name":"Vulnerabilities","score":0,"reason":"15 existing vulnerabilities detected","details":["Warn: Project is vulnerable to: GHSA-968p-4wvh-cqc8","Warn: Project is vulnerable to: GHSA-67hx-6x53-jw92","Warn: Project is vulnerable to: GHSA-v6h2-p8h4-qcjw","Warn: Project is vulnerable to: GHSA-grv7-fg5c-xmjg","Warn: Project is vulnerable to: GHSA-3xgq-45jj-v275","Warn: Project is vulnerable to: GHSA-w573-4hg7-7wgq","Warn: Project is vulnerable to: GHSA-fjxv-7rqg-78g4","Warn: Project is vulnerable to: GHSA-9c47-m6qq-7p4h","Warn: Project is vulnerable to: GHSA-952p-6rrq-rcjv","Warn: Project is vulnerable to: GHSA-xvch-5gv4-984h","Warn: Project is vulnerable to: GHSA-gcx4-mw62-g8wm","Warn: Project is vulnerable to: GHSA-c2qf-rxjj-qqgw","Warn: Project is vulnerable to: GHSA-72xf-g2v4-qvf3","Warn: Project is vulnerable to: GHSA-j8xg-fqg3-53r7","Warn: Project is vulnerable to: GHSA-3h5v-q93c-6h6q"],"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}}]},"last_synced_at":"2025-08-18T03:23:52.023Z","repository_id":9836815,"created_at":"2025-08-18T03:23:52.023Z","updated_at":"2025-08-18T03:23:52.023Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":279000724,"owners_count":26082895,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-10-08T02:00:06.501Z","response_time":56,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["component","ecs","engine","entity","es6","event","fast","game","geotic","javascript","library","npm","pattern","performance","prefab","query","rogue","roguelike","serialize","system"],"created_at":"2025-02-26T10:18:15.124Z","updated_at":"2025-10-09T01:32:27.689Z","avatar_url":"https://github.com/ddmills.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"#### geotic\n\n_adjective_ physically concerning land or its inhabitants.\n\nGeotic is an ECS library focused on **performance**, **features**, and **non-intrusive design**. [View benchmarks](https://github.com/ddmills/js-ecs-benchmarks).\n\n-   [**entity**](#entity) a unique id and a collection of components\n-   [**component**](#component) a data container\n-   [**query**](#query) a way to gather collections of entities that match some criteria, for use in systems\n-   [**world**](#world) a container for entities and queries\n-   [**prefab**](#prefab) a template of components to define entities as JSON\n-   [**event**](#event) a message to an entity and it's components\n\nThis library is _heavily_ inspired by ECS in _Caves of Qud_. Watch these talks to get inspired!\n\n-   [Thomas Biskup - There be dragons: Entity Component Systems for Roguelikes](https://www.youtube.com/watch?v=fGLJC5UY2o4\u0026t=1534s)\n-   [Brian Bucklew - AI in Qud and Sproggiwood](https://www.youtube.com/watch?v=4uxN5GqXcaA)\n-   [Brian Bucklew - Data-Driven Engines of Qud and Sproggiwood](https://www.youtube.com/watch?v=U03XXzcThGU)\n\nPython user? Check out the Python port of this library, [ecstremity](https://github.com/krummja/ecstremity)!\n\n### usage and examples\n\n```\nnpm install geotic\n```\n\n-   [Sleepy Crawler](https://github.com/ddmills/sleepy) a full fledged roguelike that makes heavy use of geotic by [@ddmills](https://github.com/ddmills)\n-   [snail6](https://github.com/luetkemj/snail6) a bloody roguelike by [@luetkemj](https://github.com/luetkemj)\n-   [Gobs O' Goblins](https://github.com/luetkemj/gobs-o-goblins) by [@luetkemj](https://github.com/luetkemj)\n-   [Javascript Roguelike Tutorial](https://github.com/luetkemj/jsrlt) by [@luetkemj](https://github.com/luetkemj)\n-   [basic example](https://github.com/ddmills/geotic-example) using pixijs\n\nBelow is a contrived example which shows the basics of geotic:\n\n```js\nimport { Engine, Component } from 'geotic';\n\n// define some simple components\nclass Position extends Component {\n    static properties = {\n        x: 0,\n        y: 0,\n    };\n}\n\nclass Velocity extends Component {\n    static properties = {\n        x: 0,\n        y: 0,\n    };\n}\n\nclass IsFrozen extends Component {}\n\nconst engine = new Engine();\n\n// all Components and Prefabs must be `registered` by the engine\nengine.registerComponent(Position);\nengine.registerComponent(Velocity);\nengine.registerComponent(IsFrozen);\n\n...\n// create a world to hold and create entities and queries\nconst world = engine.createWorld();\n\n// Create an empty entity. Call `entity.id` to get the unique ID.\nconst entity = world.createEntity();\n\n// add some components to the entity\nentity.addComponent(Position, { x: 4, y: 10 });\nentity.addComponent(Velocity, { x: 1, y: .25 });\n\n// create a query that tracks all components that have both a `Position`\n// and `Velocity` component but not a `IsFrozen` component. A query can\n// have any combination of `all`, `none` and `any`\nconst kinematics = world.createQuery({\n    all: [Position, Velocity],\n    none: [IsFrozen]\n});\n\n...\n\n// geotic does not dictate how your game loop should behave\nconst loop = (dt) =\u003e {\n    // loop over the result set to update the position for all entities\n    // in the query. The query will always return an up-to-date array\n    // containing entities that match\n    kinematics.get().forEach((entity) =\u003e {\n        entity.position.x += entity.velocity.x * dt;\n        entity.position.y += entity.velocity.y * dt;\n    });\n};\n\n...\n\n// serialize all world entities into a JS object\nconst data = world.serialize();\n\n...\n\n// convert the serialized data back into entities and components\nworld.deserialize(data);\n\n```\n\n### Engine\n\nThe `Engine` class is used to register all components and prefabs, and create new Worlds.\n\n```js\nimport { Engine } from 'geotic';\n\nconst engine = new Engine();\n\nengine.registerComponent(clazz);\nengine.registerPrefab({ ... });\nengine.destroyWorld(world);\n```\n\nEngine properties and methods:\n\n-   **registerComponent(clazz)**: register a Component so it can be used by entities\n-   **regsterPrefab(data)**: register a Prefab to create pre-defined entities\n-   **destroyWorld(world)**: destroy a world instance\n\n### World\n\nThe `World` class is a container for entities. Usually only one instance is needed,\nbut it can be useful to spin up more for offscreen work.\n\n```js\nimport { Engine } from 'geotic';\n\nconst engine = new Engine();\nconst world = engine.createWorld();\n\n// create/destroy entities\nworld.createEntity();\nworld.getEntity(entityId);\nworld.getEntities();\nworld.destroyEntity(entityId);\nworld.destroyEntities();\n\n// create queries\nworld.createQuery({ ... });\n\n// create entity from prefab\nworld.createPrefab('PrefabName', { ... });\n\n// serialize/deserialize entities\nworld.serialize();\nworld.serialize(entities);\nworld.deserialize(data);\n\n// create an entity with a new ID and identical components \u0026 properties\nworld.cloneEntity(entity);\n\n// generate unique entity id\nworld.createId();\n\n// destroy all entities and queries\nworld.destroy();\n```\n\nWorld properties and methods:\n\n-   **createEntity(id = null)**: create an `Entity`. optionally provide an ID\n-   **getEntity(id)**: get an `Entity` by ID\n-   **getEntities()**: get _all_ entities in this world\n-   **createPrefab(name, properties = {})**: create an entity from the registered prefab\n-   **destroyEntity(entity)**: destroys an entity. functionally equivilant to `entity.destroy()`\n-   **destroyEntities()**: destroys all entities in this world instance\n-   **serialize(entities = null)**: serialize and return all entity data into an object. optionally specify a list of entities to serialize\n-   **deserialize(data)**: deserialize an object\n-   **cloneEntity(entity)**: clone an entity\n-   **createId()**: Generates a unique ID\n-   **destroy()**: destroy all entities and queries in the world\n\n### Entity\n\nA unique id and a collection of components.\n\n```js\nconst zombie = world.createEntity();\n\nzombie.add(Name, { value: 'Donnie' });\nzombie.add(Position, { x: 2, y: 0, z: 3 });\nzombie.add(Velocity, { x: 0, y: 0, z: 1 });\nzombie.add(Health, { value: 200 });\nzombie.add(Enemy);\n\nzombie.name.value = 'George';\nzombie.velocity.x += 12;\n\nzombie.fireEvent('hit', { damage: 12 });\n\nif (zombie.health.value \u003c= 0) {\n    zombie.destroy();\n}\n```\n\nEntity properties and methods:\n\n-   **id**: the entities' unique id\n-   **world**: the geotic World instance\n-   **isDestroyed**: returns `true` if this entity is destroyed\n-   **components**: all component instances attached to this entity\n-   **add(ComponentClazz, props={})**: create and add the registered component to the entity\n-   **has(ComponentClazz)**: returns true if the entity has component\n-   **owns(component)**: returns `true` if the specified component belongs to this entity\n-   **remove(component)**: remove the component from the entity and destroy it\n-   **destroy()**: destroy the entity and all of it's components\n-   **serialize()**: serialize this entity and it's components\n-   **clone()**: returns an new entity with a new unique ID and identical components \u0026 properties\n-   **fireEvent(name, data={})**: send an event to all components on the entity\n\n### Component\n\nComponents hold entity data. A component must be defined and then registered with the Engine. This example defines a simple `Health` component:\n\n```js\nimport { Component } from 'geotic';\n\nclass Health extends Component {\n    // these props are defaulting to 10\n    // anything defined here will be serialized\n    static properties = {\n        current: 10,\n        maximum: 10,\n    };\n\n    // arbitrary helper methods and properties can be declared on\n    // components. Note that these will NOT be serialized\n    get isAlive() {\n        return this.current \u003e 0;\n    }\n\n    reduce(amount) {\n        this.current = Math.max(this.current - amount, 0);\n    }\n\n    heal(amount) {\n        this.current = Math.min(this.current + amount, this.maximum);\n    }\n\n    // This is automatically invoked when a `damage-taken` event is fired\n    // on the entity: `entity.fireEvent('damage-taken', { damage: 12 })`\n    // the `camelcase` library is used to map event names to methods\n    onDamageTaken(evt) {\n        // event `data` is an arbitray object passed as the second parameter\n        // to entity.fireEvent(...)\n        this.reduce(evt.data.damage);\n\n        // handling the event will prevent it from continuing\n        // to any other components on the entity\n        evt.handle();\n    }\n}\n```\n\nComponent properties and methods:\n\n-   **static properties = {}** object that defines the properties of the component. Properties must be json serializable and de-serializable!\n-   **static allowMultiple = false** are multiple of this component type allowed? If true, components will either be stored as an object or array on the entity, depending on `keyProperty`.\n-   **static keyProperty = null** what property should be used as the key for accessing this component. if `allowMultiple` is false, this has no effect. If this property is omitted, it will be stored as an array on the component.\n-   **entity** returns the Entity this component is attached to\n-   **world** returns the World this component is in\n-   **isDestroyed** returns `true` if this component is destroyed\n-   **serialize()** serialize the component properties\n-   **destroy()** remove this and destroy this component\n-   **onAttached()** override this method to add behavior when this component is attached (added) to an entity\n-   **onDestroyed()** override this method to add behavior when this component is removed \u0026 destroyed\n-   **onEvent(evt)** override this method to capture all events coming to this component\n-   **on\\[EventName\\](evt)** add these methods to capture the specific event\n\nThis example shows how `allowMultiple` and `keyProperty` work:\n\n```js\nclass Impulse extends Component {\n    static properties = {\n        x: 0,\n        y: 0,\n    };\n    static allowMultiple = true;\n}\n\necs.registerComponent(Impulse);\n\n...\n\n// add multiple `Impulse` components to the player\nplayer.add(Impulse, { x: 3, y: 2 });\nplayer.add(Impulse, { x: 1, y: 0 });\nplayer.add(Impulse, { x: 5, y: 6 });\n\n...\n\n// returns the array of Impulse components\nplayer.impulse;\n// returns the Impulse at position `2`\nplayer.impulse[2];\n// returns `true` if the component has an `Impulse` component\nplayer.has(Impulse);\n\n// the `player.impulse` property is an array\nplayer.impulse.forEach((impulse) =\u003e {\n    console.log(impulse.x, impulse.y);\n});\n\n// remove and destroy the first impulse\nplayer.impulse[0].destroy();\n\n...\n\nclass EquipmentSlot extends Component {\n    static properties = {\n        name: 'hand',\n        itemId: 0,\n    };\n    static allowMultiple = true;\n    static keyProperty = 'name';\n\n    get item() {\n        return this.world.getEntity(this.itemId);\n    }\n\n    set item(entity) {\n        return this.itemId = entity.id;\n    }\n}\n\necs.registerComponent(EquipmentSlot);\n\n...\n\nconst player = ecs.createEntity();\nconst helmet = ecs.createEntity();\nconst sword = ecs.createEntity();\n\n// add multiple equipment slot components to the player\nplayer.add(EquipmentSlot, { name: 'rightHand' });\nplayer.add(EquipmentSlot, { name: 'leftHand', itemId: sword.id });\nplayer.add(EquipmentSlot, { name: 'head', itemId: helmet.id });\n\n...\n\n// since the `EquipmentSlot` had a `keyProperty=name`, the `name`\n// is used to access them\nplayer.equipmentSlot.head;\nplayer.equipmentSlot.rightHand;\n\n// this will `destroy` the `sword` entity and automatically\n// set the `rightHand.item` property to `null`\nplayer.equipmentSlot.rightHand.item.destroy();\n\n// remove and destroy the `rightHand` equipment slot\nplayer.equipmentSlot.rightHand.destroy();\n\n```\n\n### Query\n\nQueries keep track of sets of entities defined by component types. They are limited to the world they're created in.\n\n```js\nconst query = world.createQuery({\n    any: [A, B], // exclude any entity that does not have at least one of A OR B.\n    all: [C, D], // exclude entities that don't have both C AND D\n    none: [E, F], // exclude entities that have E OR F\n});\n\nquery.get().forEach((entity) =\u003e ...); // loop over the latest set (array) of entites that match\n\n// alternatively, listen for when an individual entity is created/updated that matches\nquery.onEntityAdded((entity) =\u003e {\n    console.log('an entity was updated or created that matches the query!', entity);\n});\n\nquery.onEntityRemoved((entity) =\u003e {\n    console.log('an entity was updated or destroyed that previously matched the query!', entity);\n});\n```\n\n-   **query.get()** get the result array of the query. **This array should not be modified in place**. For performance reasons, the result array that is exposed is the working internal query array.\n-   **onEntityAdded(fn)** add a callback for when an entity is created or updated to match the query\n-   **onEntityRemoved(fn)** add a callback for when an entity is removed or updated to no longer match the query\n-   **has(entity)** returns `true` if the given `entity` is being tracked by the query. Mostly used internally\n-   **refresh()** re-check all entities to see if they match. Very expensive, and only used internally\n\n#### Performance enhancement\n\nSet the `immutableResults` option to `false` if you are not modifying the result set. This option defaults to `true`. **WARNING**: When this option is set to `false`, strange behaviour can occur if you modify the results. See issue #55.\n\n```js\nconst query = world.createQuery({\n    all: [A, B],\n    immutableResult: false, // defaults to TRUE\n});\n\nconst results = query.get();\n\nresults.splice(0, 1); // DANGER! do not modify results if immutableResult is false!\n```\n\n### serialization\n\n**example** Save game state by serializing all entities and components\n\n```js\nconst saveGame = () =\u003e {\n    const data = world.serialize();\n    localStorage.setItem('savegame', data);\n};\n\n...\n\nconst loadGame = () =\u003e {\n    const data = localStorage.getItem('savegame');\n    world.deserialize(data);\n};\n```\n\n### Event\n\nEvents are used to send a message to all components on an entity. Components can attach data to the event and prevent it from continuing to other entities.\n\nThe geotic event system is modelled aver this talk by [Brian Bucklew - AI in Qud and Sproggiwood](https://www.youtube.com/watch?v=4uxN5GqXcaA).\n\n```js\n// a `Health` component which listens for a `take damage` event\nclass Health extends Component {\n    ...\n    // event names are mapped to methods using the `camelcase` library.\n    onTakeDamage(evt) {\n        console.log(evt);\n        this.value -= evt.data.amount;\n\n        // the event gets passed to all components the `entity` unless a component\n        // invokes `evt.prevent()` or `evt.handle()`\n        evt.handle();\n    }\n\n    // watch ALL events coming to component\n    onEvent(evt) {\n        console.log(evt.name);\n        console.log(evt.is('take-damage'));\n    }\n}\n\n...\n\nentity.add(Health);\n\nconst evt = entity.fireEvent('take-damage', { amount: 12 });\n\nconsole.log(evt.name); // return the name of the event. \"take-damage\"\nconsole.log(evt.data); // return the arbitrary data object attached. { amount: 12 }\nconsole.log(evt.handled); // was `handle()` called?\nconsole.log(evt.prevented);  // was `prevent()` or `handle()` called?\nconsole.log(evt.handle()); // handle and prevent the event from continuing\nconsole.log(evt.prevent()); // prevent the event from continuing without marking `handled`\nconsole.log(evt.is('take-damage')); // simple name check\n\n```\n\n### Prefab\n\nPrefabs are a pre-defined template of components.\n\nThe prefab system is modelled after this talk by [Thomas Biskup - There be dragons: Entity Component Systems for Roguelikes](https://www.youtube.com/watch?v=fGLJC5UY2o4\u0026t=1534s).\n\n```js\n// prefabs must be registered before they can be instantiated\nengine.registerPrefab({\n    name: 'Being',\n    components: [\n        {\n            type: 'Position',\n            properties: {\n                x: 4,\n                y: 10,\n            },\n        },\n        {\n            type: 'Material',\n            properties: {\n                name: 'flesh',\n            },\n        },\n    ],\n});\n\necs.registerPrefab({\n    // name used when creating the prefab\n    name: 'HumanWarrior',\n    // an array of other prefabs of which this one derives. Note they must be registered in order.\n    inherit: ['Being', 'Warrior'],\n    // an array of components to attach\n    components: [\n        {\n            // this should be a component constructor name\n            type: 'EquipmentSlot',\n            // what properties should be assigned to the component\n            properties: {\n                name: 'head',\n            },\n        },\n        {\n            // components that allow multiple can easily be added in\n            type: 'EquipmentSlot',\n            properties: {\n                name: 'legs',\n            },\n        },\n        {\n            type: 'Material',\n            // if a parent prefab already defines a `Material` component, this flag\n            // will say how to treat it. Defaults to overwrite=true\n            overwrite: true,\n            properties: {\n                name: 'silver',\n            },\n        },\n    ],\n});\n\n...\n\nconst warrior1 = world.createPrefab('HumanWarrior');\n\n// property overrides can be provided as the second argument\nconst warrior2 = world.createPrefab('HumanWarrior', {\n    equipmentSlot: {\n        head: {\n            itemId: world.createPrefab('Helmet').id\n        },\n    },\n    position: {\n        x: 12,\n        y: 24,\n    },\n});\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fddmills%2Fgeotic","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fddmills%2Fgeotic","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fddmills%2Fgeotic/lists"}