GithubHelp home page GithubHelp logo

ruin0x11 / opennefia Goto Github PK

View Code? Open in Web Editor NEW
117.0 13.0 18.0 35.16 MB

(Archived) Moddable engine reimplementation of the Japanese roguelike Elona.

License: MIT License

Emacs Lisp 0.53% Shell 0.01% Batchfile 0.02% Python 0.15% Lua 98.98% GLSL 0.05% TypeScript 0.27%
game engine clone lua love2d love2d-game rpg roguelike roguelike-game elona

opennefia's Introduction

Note

Development has moved to https://github.com/OpenNefia/OpenNefia.git. This repository is for an older prototype of OpenNefia that is no longer supported. The source code in this repository will be used as a reference for the final version of OpenNefia.


OpenNefia

OpenNefia is an open-source engine reimplementation of the Japanese roguelike RPG Elona using Lua and the LÖVE game engine.

It is not a variant of Elona, but a ground-up rewrite intended to vastly expand the support for modding in the game, allowing for game features that would have been impractical or impossible with the vanilla codebase.

It is also an experiment to see if a game can be written in an extensible manner, using a mod system and an extensive API.

Note that it is currently alpha-quality and massive breakage will occur when attempting to play through the game normally. Nearly everything is still a work-in-progress. If you would like a stable experience, then please play vanilla Elona or a variant like oomSEST instead.

See the wiki for more information and a work-in-progress modding tutorial.

Features

  • Intended goal of being feature-compatible with Elona 1.22 (though still a work in progress).
  • Ability to modify the flow of game logic using event hooks.
  • Architecture based on APIs - mods can reuse pieces of functionality exposed by other mods.
  • Quality-of-life features for developers like code hotloading and an in-game Lua REPL. Build things like new game UIs or features in an interactive manner.
  • Supports Windows, macOS, and Linux.

Running

If you're using Windows, install LÖVE from the official website. (Make sure it's the 64-bit version.)

If you're using a Unix-like platform, ensure the love binary, wget and unzip are on your PATH.

Then, run OpenNefia.bat (Windows) or OpenNefia (Unix).

Contributing

OpenNefia uses the Gitflow workflow. Please branch off of develop when developing new features or porting things. master will be reserved for stable releases and hotfixes only once the engine becomes stable enough to use.

See CONTRIBUTING.md for more details.

See docs/ARCHITECTURE.md for a high-level overview of the codebase.

Credits

See CREDITS.md for third-party code information.

opennefia's People

Contributors

ki-foobar avatar ruin0x11 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

opennefia's Issues

Rethink map generation system using templates

In OpenNefia, you can generate a new map from scratch as an object, and manipulate it without it needing to be the current map. This convenience comes with a caveat, however: a lot of maps have things like character filters, which are functions that determine what characters to spawn in the map. Because these are Lua functions they can't be serialized directly on the map in a portable manner without doing something like writing the source code as a string, so in order to persist them across saves they have to be stored in a static definition somewhere, like a elona_sys.map_template entry. This limits the use of programmatically generating maps if the only way to do certain important things with maps is by generating the map using the map_template system. At that point all maps might as well be generated with map_template anyway, and this implicit dependency is used in many places in the code: too many places make the assumption the map was generated using map_template in order to get at this non-serializable data.

This could be solved in at least three ways:

  1. Add some system to save functions on top of maps, for example creating a special string and calling loadstring on it at runtime.
  2. Force all maps to be generated through map_template, making it the "blessed" way of generating maps.
  3. Force all maps to be associated with a map_template before they will be accepted for saving, if they are created programmatically using InstancedMap:new().

Add theming system

It should be easy to add new tile themes in a non-destructive manner, removing the need for adding the "user_*.bmp" files by hand. There are two parts to this:

  1. Add an ability to choose which tile override to use for a character/item/etc out of multiple. This is because there are already a lot of different variations of each one, from numerous different theme packs from vanilla. You should also be able to select any arbitrary tile as the replacement, and not just the recommended ones.
  2. Allow saving "sets" of changed tiles somehow.
  3. Expose the sets of changed tiles through mods, so you could have a "beautify" mod you can toggle on and off at will.

Of course, this should all be possible to do without the need to modify anything on the filesystem. It should be as easy as a config change.

Maximum range of all skills are off by one

Skill range is actually defined like this in vanilla:

#define global ctype skillTgRange(%1)	(sdataRef@(3,%1)¥extTg+1)

So we have to make sure to increment some of the ported skills to have range+1.

Version the public API

Related Issues

Description

The API needs to be versioned if it's to be used publicly.

I'm thinking there should be a monotonically incrementing number, accessable through Env.api_version(), so it's easily compared against a target version with comparison operators. Every time a file under api/ is changed in an incompatible manner, the number should be incremented. "Incompatible" could be ambiguous so we could just use the heuristic of changing any file under api/, but this could be confusing for things like bug fixes or optimizations that don't affect existing code. Of course, it's possible to get it wrong also and accidentally affect behavior that shouldn't have been changed, so it might be a good idea to just increment it always.

Also there needs to be extremely clear documentation over each change. Pull requests that modify api/ should not be accepted without recording the changes made in a machine-readable format. This is to enable searching for changes between versions more easily.

An an example of this use case, it's difficult to search through Minecraft's documentation over changed API methods/classes in the MCP project, because some of it is recorded in Markdown instead of a machine-readable format with a standardised schema. Since it also uses an IRC bot instead of a website to search for things you have to sign up for and use IRC to access the data, increasing friction.

Ideally, the way this system is designed should make it possible to create a website which provides:

  • A list of changes per API version. They should be typed per the change (added/removed method argument, added/removed function, etc.) and machine readable as well as formattable.
  • A way to search for an identifier across all API versions and find where it was added/removed/changed.
  • A way to search for a specific API version to know what was changed. This will be really useful when you start seeing code like if Env.api_version() < 26 then ... end and want to know what was added/removed in that version.

I'm not sure of the extent that semver should be used for the base distribution, since pull requests can implement a single API method without necessitating a whole version bump. Supporting both an integer version number and semver means having two sources of truth, though. Maybe major releases could be defined as constant integers per the API version they represent. Semver also has its own issues if you make behavioral changes based on the kind of version incremented, because different people can have different ideas of what constitutes "major" or "minor", and it can easily be misused otherwise.

Once this system is in place we can't go back, so it should be carefully thought over.

Redesign skill querying API

The skill querying API in ICharaSkills.lua is too confusing. Currently it looks like this:

chara:skill_level(skill_id)
chara:magic_level(magic_id)
chara:resist_level(element_id)

These three functions do nearly the same thing, but operate on different data types. Instead it should look like this:

chara:skill_level(skill_id)
chara:skill_level(magic_id, "elona.magic")
chara:skill_level(element_id, "base.element")

That way new methods don't have to be defined if another mod wants to use the skill/potential system.

Namespace custom game object fields by the mod that adds them

Many games like Minecraft have systems where mods can attach custom data to game objects. In that game specifically, groups of custom fields are placed in tagged bundles, and mods check if the object has a specific bundle if they want to use fields from other mods.

This is in contrast to how OpenNefia currently handles custom fields added by mods, in which... it doesn't. Mods simply add whatever data they want directly on the object. This can cause numerous issues:

  • Names of fields can collide between mods, and there would be no way of knowing this until the incompatible mods are used simultaneously.
  • It's not possible to easily figure out what mod added a specific field.
  • This has to be settled early on, or it will cause massive backwards compatibility issues further down if it doesn't end up working out (if it's even possible to make changes to things this fundamental).

There could be multiple ways of handling these issues:

  1. Keep a centralized repository of fields added by mods using the existing schema system, and warn the author if they try to publish a mod that causes conflicts. This means mod authors have to manually declare what fields they could add to data types.
  2. Force mods to add new fields on a subfield of game objects, like object.m.my_mod.field. This increases verbosity, but makes it completely unambiguous which mod the field is concerned with and eliminates concerns about naming conflicts. After this is done, __newindex on the game object can be overridden to make it impossible to add new fields directly on the object's table.

Organize the ported code better

Related Issues

#30.

Description

The code needs to be better organized. Random bits of code are thrown into API modules with no real sense of separation, and a lot of the game's logic is coded as event handlers which are put into massive files, where each handler has little relationship to the ones nearby. Since moving things will break the public API it should be done sometime soon.

However, this is lower priority in the face of making sure things work, so it will probably only get addressed once the engine is usable.

Consider allowing callback instead of map_generator in map_template

Say you would like to add a new map for a quest. The bare minimum amount of code to do this is:

data:add {
  _type = "base.map_generator",
  _id = "my_quest",
  generate = function()
    return InstancedMap:new(50, 50), "quest_map"
  end
}

data:add {
  _type = "elona_sys.map_template",
  _id = "my_quest",
  map = {
    generator = "my_mod.my_quest",
    params = {}
  },

  copy = {
    is_indoor = true
  }
}

This is a bit inconvenient since you always have to have a base.map_generator before adding the map template. It might be nicer if instead this was allowed:

data:add {
  _type = "elona_sys.map_template",
  _id = "my_quest",
  map = function()
    return InstancedMap:new(50, 50), "quest_map"
  end

  copy = {
    is_indoor = true
  }
}

This essentially means that elona_sys.map_template's main purpose becomes copying unserializable fields like functions to a map when it is loaded.

Also, as noted in #13, we should probably make elona_sys.map_template first-class and make every map associated with a template before it's saved, to reduce confusion regarding the map generator system. It was originally added as a way to insert new dungeon types, but a map function like the one above along with something like elona_sys.dungeon_generator would suffice. base.map_generator almost seems redundant when phrased like this.

Double-check special case behaviors for skills/spells

As with a lot of code in vanilla, there are some parts that involve spellcasting where a different code path is taken if a specific spell or skill is used. Examples are numerous, and include things like Wish and Wizard's Harvest having different calculations for spell failure than the rest of the spells. We should do a once-over of all the effect identifiers after most of the code is ported to be sure we didn't miss any.

Implement an actual version control policy

Related Issues

#44

Description

Currently there's only a master branch, where all the changes have been pushed so far. This isn't sustainable in the long term. At a minimum, there should be a develop or similar branch for pushing bleeding-edge changes.

Add better ways of appending things in locale files

Sometimes it is desirable to modify locale files by adding new entries to a list-like namespace, without caring about the specific order of things. Right now the system is too kludgy to allow for this. Consider the following:

return {
   names = {
      _1 = "Lomias",
      _2 = "Larnneire"
   }
}

Basically there should be a way of programmatically adding an entry _3. The way this would be used typically is calling I18N.get_choice_count("names"), which would return 3, then selecting one of the entries and remembering the index of which one was chosen.

One specific place this would be used for is for generating the text of random quests in the quest boards from a set of choices (see here).

Allow taking IMapObject in Gui.mes_visible()

There's a lot of code in vanilla that looks like

if sync(cc) : txt "" + name(cc) + " is a thing."

This means "only print this message if the character at cc is in sight". Right now this is ported as

Gui.mes_visible("example.is_a_thing", chara.x, chara.y, chara)

But for some reason I'm anxious it should actually be this instead:

Gui.mes_visible("example.is_a_thing", chara, chara)

Or maybe this.

if chara:is_in_fov() then
   Gui.mes("example.is_a_thing", chara)
end

It's just such a common and pervasive pattern in the codebase that I feel it should be done right the first time.

Add API documentation site

Eventually there should be an ldoc site holding the mod API. However, as everything is rather unstable it would probably be more helpful to simply read at the source code at the moment.

Consider bundling custom LÖVE build (for MIDI support, etc.)

There is a custom build of LÖVE I worked on located here, which adds support for MIDI music using native operating system APIs. The actual code is taken from the adaption of Timidity for use in the ZDoom project. If it can't be merged into the official branch, it might have to be bundled with the engine code somehow.

There are also some other features missing in stock LÖVE that are supported by HSP's graphics API, such as support for bold/italic text and CJK text wrapping, so maintaining a custom fork of LÖVE may become necessary at some point.

Figure out how to layout extra HUD widgets

I'm not sure how the HUD system should be designed. Currently it uses the same logic for HUD widget layouting by fixed offsets and widgets have no awareness of each other. For example, say you want to remove the message window and replace it with a floating text log-like widget. Because the HP and MP bars are fixed in place, there will be a gap between them and the bottom bar when doing this. I'm thinking that the bars would need knowledge of being positioned relative to the top of the message window, and if the position of the message window changes then they should be moved downwards automatically. But someone else could place other things in the same space, potentially, and it seems to get complicated if you have two widgets that are supposed to occupy the same screen space (like replacing the default clock with a different one).

Minecraft seems to do something reasonable, as a few of its most popular mods feature widgets like minimaps, so there should be some way of doing this, but I haven't thought about it deeply.

Improve performance of Gui.update_screen()

This function gets called all the time, so we should optimize it.

It's mainly dependent on how fast the draw layers finish updating on a full screen refresh. Here is a quick benchmark:

[debug][api.Stopwatch] [update_draw_pos]	0.02408ms	(0.00 frames)
[debug][api.Stopwatch] [calc_screen_sight]	2.45786ms	(0.15 frames)
[debug][api.Stopwatch] [update_input]	0.63419ms	(0.04 frames)
[debug][api.Stopwatch] [update_renderer]	4.18282ms	(0.25 frames)
[debug][api.Stopwatch] [update_hud]	36.02600ms	(2.16 frames)

[debug][api.Stopwatch] [screen update]	52.80805ms	(3.17 frames)

So the HUD probably needs some work.

[debug][api.Stopwatch] [hud_hp_bar]	0.01597ms	(0.00 frames)
[debug][api.Stopwatch] [hud_mp_bar]	1.06812ms	(0.06 frames)
[debug][api.Stopwatch] [hud_hud_level]	0.42892ms	(0.03 frames)
[debug][api.Stopwatch] [hud_status_effects]	0.44608ms	(0.03 frames)
[debug][api.Stopwatch] [hud_stats_bar]	0.47994ms	(0.03 frames)
[debug][api.Stopwatch] [hud_clock]	0.37408ms	(0.02 frames)
[debug][api.Stopwatch] [hud_minimap]	9.08399ms	(0.55 frames)
[debug][api.Stopwatch] [hud_skill_tracker]	0.45085ms	(0.03 frames)
[debug][api.Stopwatch] [hud_buffs]	0.35095ms	(0.02 frames)

Also, we really need profiling tools that are trivial to use, so we don't have to keep inserting calls to Stopwatch:p() everywhere.

Add event `base.on_hud_refreshed`

This event is sorely lacking when building new HUD elements to be modded in. Basically this should get triggered on Gui.update_screen() and synchronize each widget with the game state.

Remove class delegation of bare fields

Description

class:delegate() allows you to delegate member fields to child classes, but this massively increases complexity and confusion by overriding __index and __newindex on the generated object. This way you can't easily tell what's being delegated or a plain table access if you don't remember the interface, and accessing bare fields instead of using getters and setters is bad practice anyways.

Ideally all delegation should do is generate methods that pass self and the arguments to the child metatable's method, letting us not have to override __index and __newindex, by only allowing delegation of functions.

Remove Event.define_hook()

It's just a needless pile of abstraction over Event.register(). There ought to be one blessed way of doing things throughout the API.

Standardize localization conventions for data types

It is a very common need to attach some localized strings to data entries, like character names. Currently the namespacing for this in locale files is not standardized. It should be consistent across all data types, to make it clear to modders that the strings belong to a data type.

One example of this could be:

_.base.scenario.my_mod.test_scenario.name

Here, the format is as follows:

_.<data_type>.<data_id>.<key>

Laying out the localization like this would allow for creating APIs that unify localizing data entries, perhaps like this:

local name = data["base.chara"]:localize("elona.putit").name

This would be generic across everything in data without the need to write a special function for every single data type.

Add BMP loading with key color to LOVE runtime

Related to #14.

LOVE has no support for reading BMP files with a key color. There is a BMP reader implemented in Lua, but it's slow and uses a lot of memory. Adding support directly in LOVE could solve these problems, and it would also remove the need to use libvips.

Remove implicit 'map' argument in API functions

You can specify which map to operate on in may API functions by passing in an optional parameter, defaulting to the current global map, like so:

local tile = Map.tile(10, 10) -- same as Map.tile(10, 10, Map.current())

local map = InstancedMap:new(50, 50)
tile = Map.tile(10, 10, map)

This is convenient, but could lead to unexpected behavior if the user forgets to add the map argument even though they intended to. This has happened to the author on several occasions. This is annoying since there's no indication of this mistake except for mutated state in the current map. Then you have to figure out where the global map is being mutated from, which is not always obvious.

Making this a required parameter forces you to be explicit about the map you use.

local map = Map.current()
local tile = Map.tile(10, 10, map)

However, this would mean you'd no longer be able to write short one-liners like this (in the REPL, for example):

Chara.create("elona.putit")

Instead, you'd have to do this:

Chara.create("elona.putit", nil, nil, {}, Map.current())

(Of course, you could just write a replacement for Chara.create() for prototyping purposes that has the old behavior.)

Essentially, this would be a tradeoff of convenience for maintainability. Also, this is one of those design issues that's really hard to change later, so it has to be set in stone early on.

Rewrite default AI and character relationship/faction methods

This is currently the part of the code that gives me the most anxiety. I'm not sure I ported it correctly, since I made too many changes to how the relationship system works between characters of different factions (enemy/ally). It also has a lot of unimplemented placeholder features, like the party system.

We should go back to basics and do a straight port of Elona's relationship system, rewriting the default AI in the process to use all of the features that hadn't been ported yet at the time.

Add serialized map header/metadata

There are a lot of places where we need some small amount of information about a map/area without needing to load the entire map into memory. Examples include the conquered dungeon status of a map, to display an icon over its entrance in the world map. There could be a small "header" that is serialized alongside the main map data that contains this data. Then there could be a field like map.header where anything inside is saved to the header.

Use numeric enum instead of string for identify state/curse state/etc.

Using string enums makes it way harder to compare two enums together, leading to code like this and completely unnecessary functions like Effect.is_cursed() just to do a comparison check. We should use the Enum module instead and either have people import it or use it as a global.

They should still be plain numbers instead of classes to reduce magic.

Allow specifying render width/height of tile draw layers

For the screenshot feature to work, we'd have to be able to ask the renderer to output an image at the same size as the entire map the player is in. But currently there is no width or height parameter to determine the width and height of the visible area in IDrawLayer:draw(x, y). We should force this method to accept width and height also, without having to use ICoords:get_start_offset().

Save updating mechanism

It will be important to have some way of updating the save between versions, like if a field on a game object is changed.

But we have to be careful that mods can properly provide the list of game objects to upgrade. What if a mod places a character object somewhere in its save data, instead of a location? If we're trying to update a field on IChara from the save migration, how will it be possible for us to know that the save has an IChara object on it?

  1. Brute force it by iterating everything that looks like a table recursively until we find an IChara object. This will be very slow and might not even work in all cases.
  2. Provide an interface for mods to expose the objects they save, so you can do something like Map.iter_all_objects("base.chara"). This will iterate absolutely everything that's serialized onto the map, and nested in containers or special tables and such. The problem with this approach is that if the mod is buggy, the save migration might fail or the migration process will miss a few objects it doesn't know about, so when those objects get placed into the map they'll still be using the outdated fields.
  3. Tag all serialized game objects with a version number of the game they were saved in. If any map or other ILocation tries to take the object and there is a save migration needed, perform all the migration work needed on that object at the point the it's placed in the world. The version number could just be the same as the API version number as in #23. But we have to be careful to allow mods to use this per-object migration feature as well, or they might get stuck if they try to use an object in a weird way not covered by the base API.

Remove color argument to calls to Draw and asset methods

They cause unnecessary table allocations, and you can just use Draw.set_color() instead for slight performance gains from not having to set the color between multiple assets each time. It would be better if we used Draw.set_color() as the blessed way of setting colors. Also we should remove the option of passing a table argument to Draw.set_color() as well.

This really needs to be done before the API gets too popular.

Redesign system for connecting maps to one another (with stairs, etc.)

The system for connecting maps to one another really needs to be redesigned.

The main issue stems from a fundamental difference in how maps are managed from vanilla. In vanilla, each unique map is referred to by a unique integer ID, which always refers to a single instance of that map. In OpenNefia this is no longer the case: you can have more than one map created from the same template, each with a different ID. There are a few problems this approach creates:

  • There is no longer a map property that uniquely identifies the map. In some cases we want to know if this is "the" instance of a map created from a template. In vanilla it suffices to check if the integer ID is equal to that of the map template. The current kludge for this is a field named "gen_id", which is intended to be a unique string identifying the map based on what generated it. (this field is still not unique across multiple map instances)
  • A consequence of the above is that when a map is refreshed, the stairs leading to external maps need to be "reconnected" to any existing generated map used in the map before it was refreshed. I think in order to solve this issue the stairs could be tagged with a unique string, then the newly refreshed map and the old map are compared for any stairs that match the same tag. For those that do, associate the stairs between the maps.
  • It's no longer possible to do something like Map.load_from_id(MAP_ID_VERNIS) due to the above, so we have to do things like search all of the maps in the save file to find the one we want.

Merge base, elona, elona_sys and the api/ folder into one base distribution

Trying to modularize things has resulted in more pain than benefit.

  • It's hard trying to make everything perfectly clean and available for mods while also making sure it isn't buggy. Even just putting code in its own module is clean enough. Trying to add event handling and mod-specific code on top of that only greatly increases the development time that could be put to better use elsewhere. It would be better to just think about those problems as separate from making the game playable as a straight port of vanilla ASAP, so people will actually be able to play through it and give feedback or contribute.
  • There are too many unforeseen dependencies between parts of the code, like parts expecting this one skill ID that's hardcoded but actually defined in the elona mod, that would have to be resolved at some point if the insistence on keeping things modular remains. The vanilla codebase was never really meant to be modularized like this anyway since it's one giant monolith that was not designed for this kind of separation of concerns in mind. So instead I think the base content should be put into the main distribution instead.
  • The modularization thing was mainly just to "prove" that the engine was well constructed enough to support modding, but I think that's more or less a solved question at this point. There are barely any globals and aside from the exceedingly confusing organization scheme for all the code and data, which is greatly exacerbated by this modularization issue, things have been cleaned up quite a bit. And it isn't like the modular components can't be kept as standalone libraries. We can put them under a separate namespace, like api.lib.scene.Scene or api.lib.mapgen.Elona122Map or something like that.
  • We aren't trying to be a general-purpose roguelike engine like T-Engine or libtcod or something - merely a moddable way of playing Elona or similar derivations of it with basically the same game systems in place. So with those assumptions in place we can stop worrying if all these features like the game menus in the base API under api.gui.menu should be hardcoded, because those features are exactly what makes the engine a reimplementation of Elona, so they should be more or less hardcoded. People are still free to ignore them and code up something else as part of a mod if those features don't suit their needs.

So basically this means giving up trying to modularize everything while also trying to make it correct at the same time, porting the code directly from the HSP source without anything fancy, and only trying to make it moddable once all that work has been done and everything is at least playable. In the end there should just be the base engine code and one elona mod containing all the data, which we will always assume is available in order for the engine to function.

What this allows for is:

  • No more confusion as to which mod you should place things in.
  • Addition of really common functions that depend on hardcoded algorithms, like the skill experience system being based on traits and stats. This means you don't have to call Skill.gain_skill_exp(chara, skill) but instead chara:gain_skill_exp(skill).

Another reason for this system with the three separate mods was the separation of base content from addon content, where you might not want Elona's characters or items if you're writing a custom scenario. This is because the generation system is based on iterating the entire set of existing definitions for characters/items across all mods and picking one at random (the same as vanilla). But this complaint doesn't make sense since you should be able to have two mods with totally separate loot systems side by side without having to remove the entire elona mod to limit the scope to your custom scenario's mod.

For that problem, where the generation probabilities are currently global, we should refactor the code to take the chances for generating things as defaults and instead feature a global "loot table" system where you could swap in a different loot table entirely, like if you don't want any of Elona's critters showing up in a custom scenario.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.