Engine Overview

Understanding Crea’s engine is not necessary for simple mods but can prove to be instrumental for more advanced mods. There are several elements at work in the engine however the most fundamental aspect of it is that it’s a component-based engine.

A component-based engine means that there is a single Entity that is composed of many Components. Such as a Broadsword is an Entity that consists of a ItemComponent, WeaponComponent and EquipmentComponent. Generally any combination of components can be added to a single Entity.

While components usually contain data and sometimes logic, each Subsystem is responsible for a single type of component and keeping them together. Such as the CraftSystem is responsible for creating each CraftComponent for every item and tracks all of these components for easy access.

game.craft.getComponents()

All Subsystems are registered with Game and are accessable at anytime using its name as attribute.

Subsystems are directly associated with one or more components but a normal system is standalone. A system is used to track grouped logic and data over many frames. Just like Subsystems, Systems are updated every frame and can be easily accessed at anytime.

Entity Creation

Every Entity type is represented by a .ce (Crea Entity) file. When Crea loads it finds all .ce files and creates a stamp for each one which is used in the future anytime a new entity needs to be created. Such as a Oil Slime entity stamp is created so that anytime a new Oil Slime is spawned it is based off of this stamp. Along with the stamp is a vanilla Content Entities for each type which makes it easy to query information about an entity without having to create a new one.

The EntityManager is responsible for creating an maintaining all entities. Use EntityManager.create() to create new entities.

oilSlimeEntity = game.entity.create('oil_slime')
# When an entity is no longer needed it can be destroyed directly
oilSlimeEntity.destroy()

Note

Remember to fully setup the entity with a position and realm!

from siege.world import World
from siege.world.realm import Layer

world = World.get()
player = world.getPlayer()
oilSlimeEntity = game.entity.create('oil_slime')
# Set the Oil Slime directly above the player
oilSlimeEntity.setInitialPosition(player.entity.getPosition() + Vector(0, -30))
world.move(oilSlimeEntity, player.entity.realm, Layer.Active)

Content Entities

A Content Entity is a vanilla entity that was created directly from its stamp and is unmodified making it easy to query information about an entity type. Every Content Entity has a id set to 0 since they are not unique. It is possible to query any entity if it is a content entity with Entity.isContentEntity().

entity.isContentEntity()

A Content Entity can be retrieved at anytime through Content.entity.

lumberContent = game.content.get('lumber')  # Get lumber content
lumberContent.entity.item.sellPrice  # Get lumber sell price through its Content Entity

Note

You should only ever query information from Content Entities!

Creating Components

It is entirely possible to create a new Component type and use it in your own entities. Start with creating a .py file in mods/mymod/component/. Here is an example which is thoroughly commented. See the documentation for Component and ComponentDefinition for full details on the classes including all functions that can be overridden.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
from siege import game
from siege.component import Component, ComponentDefinition, ComponentFactory


class Example(ComponentDefinition):
    def __init__(self):
        ComponentDefinition.__init__(self)
        # Any amount of attributes containing data can be used here
        self.data = {}
        # Freeze this so that no more attributes can be set on it help prevent any errors
        self.freeze()

    def getType(self):
        '''Return the type of component this definition is associated with.'''
        return ExampleComponent.TYPE


class ExampleComponent(Component):
    TYPE = "example"  # The type of the component
    CID = 0  # The Content ID which is automatically assigned during registration
    VERSION = 1  # The current version of this component. This should be incremented when the data in read()/write() changes.

    def __init__(self, definition):
        Component.__init__(self)
        # Transfer over any data we need from the definition (Example instance)
        self.data = definition.data
        self.freeze()

    def getData(self, key):
        # Any number of methods can be defined and used in a Component
        return self.data[key]

    @staticmethod
    def factory(entity, componentType, definition):
        # A Component can exist without its own Subsystem. It is then registered to a generic 'BasicSystem'.
        # When this is the case Component assumes the responsibility of creating itself and assigning itself to the entity.

        if entity.isContentEntity():
            # Set component to None here so that the BasicSystem does not manage the component for a Content Entity
            entity.add(ExampleComponent(definition))
            component = None
        else:
            component = ExampleComponent(definition)
            entity.add(component)
        return component

    @staticmethod
    def register():
        # Request a unique CID which is used internally for several things such as in multiplayer
        ExampleComponent.CID = game.entity.requestCid(ExampleComponent.TYPE, Example)
        # Next register the component with the engine and provide a factory from which components will be created
        # NOTE: This is only needed if the Component does not have a corresponding Subsystem. If it does then the Subsystem registration takes this responsibility.
        game.registerComponent(ExampleComponent.TYPE, ComponentFactory.create(ExampleComponent.factory))

You’ll notice on line 5 the Example class derives from ComponentDefinition. This is the stamp part that is used in .ce files to hold the data until an actual ExampleComponent is created through ExampleComponent.factory. The interface for a ComponentDefinition should be kept simple since it is directly used the most.

Using an example from core, we have mods/core/component/imbue.py for the ImbueComponent which is responsible for tracking the results for imbuable items. You will notice ImbueComponent only contains static data so it does not need ImbueComponent.pack(), ImbueComponent.unpack(), ImbueComponent.read(), or ImbueComponent.write(). For more examples be sure to check out the mods/core/component/ directory.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
from core.tuning import RemnaType

from siege import game
from siege.component import Component, ComponentDefinition, ComponentFactory


class Imbue(ComponentDefinition):
    def __init__(self):
        ComponentDefinition.__init__(self)
        self.remnaType = RemnaType.All
        self.results = {}
        self.freeze()

    def getType(self):
        return ImbueComponent.TYPE


class ImbueComponent(Component):
    TYPE = "imbue"
    CID = 0
    VERSION = 1

    def __init__(self, definition):
        Component.__init__(self)
        self.remnaType = definition.remnaType
        self.results = definition.results
        self.freeze()

    def isCompatible(self, entity):
        remna = entity.remna
        return bool(self.remnaType & remna.element) and remna.tier in self.results

    def getContent(self, tier):
        return game.content.get(self.results[tier])

    @staticmethod
    def factory(entity, componentType, definition):
        if entity.isContentEntity():
            entity.add(ImbueComponent(definition))
            # Set component to None here so that the BasicSystem does not manage the component for a Content Entity
            component = None
        else:
            component = ImbueComponent(definition)
            entity.add(component)
        return component

    @staticmethod
    def register():
        ImbueComponent.CID = game.entity.requestCid(ImbueComponent.TYPE, Imbue)
        game.registerComponent(ImbueComponent.TYPE, ComponentFactory.create(ImbueComponent.factory))

Creating Subsystems

For some new components you will also want to create a new class:Subsystem to manage the component. Below is an example subsystem with comments. See the documentation for SubSystem for full details on the class including all functions that can be overridden.

from mymod.component.example import ExampleComponent

from siege import game
from siege.subsystem import Subsystem


class ExampleSystem(Subsystem):
    NAME = "example"

    def getName(self):
        return self.NAME

    def __init__(self):
        Subsystem.__init__(self)
        self.components = []
        # Freeze the subsystem to prevent additional attributes being accidentally added
        self.freeze()

    def create(self, entity, type, definition):
        # When providing a subsystem for a component it becomes responsible for creating new components
        # This means that the component does not need a factory method
        component = ExampleComponent(definition)
        self.components.append(component)
        entity.add(component)

    def add(self, entity):
        # Add an entity that already has the component created
        if entity.has(ExampleComponent.CID):
            self.components.append(entity.example)

    def remove(self, entity):
        # Remove an entity/component from being tracked by this subsystem
        try:
            del self.components[self.components.index(entity.example)]
        except (AttributeError, ValueError):
            pass

    @staticmethod
    def register():
        # Register the subsystem with Game
        game.registerSubsystem(ExampleSystem(), ExampleSystem.NAME, ExampleComponent.TYPE)

For more examples see mods/core/system/ and look for classes that inherent from Subsystem.

Creating Systems

Sometimes you will need a system to manage data and perform logic every frame but not be responsible for a Component. This is possible by registering a simple system with Game. Below is an example system.

from siege import game


class ExampleSystem(object):
    NAME = "example"

    def getName(self):
        '''This is a required method.'''
        return self.NAME

    def __init__(self):
        pass

    def update(self, frameTime):
        '''Called every frame.
        :param frameTime: The amount of time (ms) passed since the last frame.
        '''
        pass

    @staticmethod
    def register(world):
        '''Registers this System to Game making it accessable through game.example.'''
        game.registerSystem(ExampleSystem.NAME, ExampleSystem())

    @staticmethod
    def unregister(world):
        '''Unregisters this System from Game.'''
        if game.hasSystem(ExampleSystem.NAME):
            game.unregisterSystem(ExampleSystem.NAME)

For more examples see mods/core/system/ and look for classes that are registered using Game.registerSystem().