mirror of
https://github.com/Relintai/pandemonium_engine_minimal.git
synced 2025-01-19 01:07:17 +01:00
Removed translations.
This commit is contained in:
parent
ddf2447835
commit
26462a0392
2432
doc/Doxyfile
2432
doc/Doxyfile
File diff suppressed because it is too large
Load Diff
35
doc/Makefile
35
doc/Makefile
@ -1,35 +0,0 @@
|
||||
BASEDIR = .
|
||||
CLASSES = $(BASEDIR)/classes/ $(BASEDIR)/../modules/
|
||||
OUTPUTDIR = $(BASEDIR)/_build
|
||||
TOOLSDIR = $(BASEDIR)/tools
|
||||
JSDIR = $(BASEDIR)/../platform/javascript
|
||||
LANGARG ?= en
|
||||
LANGCMD = -l $(LANGARG)
|
||||
|
||||
.ONESHELL:
|
||||
|
||||
clean:
|
||||
rm -rf $(OUTPUTDIR)
|
||||
|
||||
doxygen:
|
||||
rm -rf $(OUTPUTDIR)/doxygen
|
||||
mkdir -p $(OUTPUTDIR)/doxygen
|
||||
doxygen Doxyfile
|
||||
|
||||
markdown:
|
||||
rm -rf $(OUTPUTDIR)/markdown
|
||||
mkdir -p $(OUTPUTDIR)/markdown
|
||||
pushd $(OUTPUTDIR)/markdown
|
||||
python2 $(TOOLSDIR)/makemd.py $(CLASSES)
|
||||
popd
|
||||
|
||||
rst:
|
||||
rm -rf $(OUTPUTDIR)/rst
|
||||
mkdir -p $(OUTPUTDIR)/rst
|
||||
python3 $(TOOLSDIR)/make_rst.py -o $(OUTPUTDIR)/rst $(LANGCMD) $(CLASSES)
|
||||
|
||||
rstjs:
|
||||
rm -rf $(OUTPUTDIR)/rstjs
|
||||
mkdir -p $(OUTPUTDIR)/rstjs
|
||||
npm --prefix $(JSDIR) ci
|
||||
npm --prefix $(JSDIR) run docs -- --destination $(OUTPUTDIR)/rstjs/html5_shell_classref.rst
|
154
doc/class.xsd
154
doc/class.xsd
@ -1,154 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<xs:schema attributeFormDefault="unqualified" elementFormDefault="qualified" xmlns:xs="http://www.w3.org/2001/XMLSchema">
|
||||
<xs:element name="class">
|
||||
<xs:complexType>
|
||||
<xs:sequence>
|
||||
<xs:element type="xs:string" name="brief_description" />
|
||||
<xs:element type="xs:string" name="description" />
|
||||
<xs:element name="tutorials">
|
||||
<xs:complexType>
|
||||
<xs:sequence>
|
||||
<xs:element name="link" maxOccurs="unbounded" minOccurs="0">
|
||||
<xs:complexType>
|
||||
<xs:simpleContent>
|
||||
<xs:extension base="xs:string">
|
||||
<xs:attribute type="xs:string" name="title" use="optional" />
|
||||
</xs:extension>
|
||||
</xs:simpleContent>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
<xs:element name="methods" minOccurs="0">
|
||||
<xs:complexType>
|
||||
<xs:sequence>
|
||||
<xs:element name="method" maxOccurs="unbounded" minOccurs="0">
|
||||
<xs:complexType>
|
||||
<xs:sequence>
|
||||
<xs:element name="return" minOccurs="0">
|
||||
<xs:complexType>
|
||||
<xs:sequence>
|
||||
<xs:sequence />
|
||||
</xs:sequence>
|
||||
<xs:attribute type="xs:string" name="type" />
|
||||
<xs:attribute type="xs:string" name="enum" use="optional" />
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
<xs:element name="returns_error" maxOccurs="unbounded" minOccurs="0">
|
||||
<xs:complexType>
|
||||
<xs:sequence>
|
||||
<xs:sequence />
|
||||
</xs:sequence>
|
||||
<xs:attribute type="xs:byte" name="number" />
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
<xs:element name="argument" maxOccurs="unbounded" minOccurs="0">
|
||||
<xs:complexType>
|
||||
<xs:sequence>
|
||||
<xs:sequence />
|
||||
</xs:sequence>
|
||||
<xs:attribute type="xs:byte" name="index" />
|
||||
<xs:attribute type="xs:string" name="name" />
|
||||
<xs:attribute type="xs:string" name="type" />
|
||||
<xs:attribute type="xs:string" name="enum" use="optional" />
|
||||
<xs:attribute type="xs:string" name="default" use="optional" />
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
<xs:element type="xs:string" name="description" />
|
||||
</xs:sequence>
|
||||
<xs:attribute type="xs:string" name="name" use="optional" />
|
||||
<xs:attribute type="xs:string" name="qualifiers" use="optional" />
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
<xs:element name="members" minOccurs="0">
|
||||
<xs:complexType>
|
||||
<xs:choice maxOccurs="unbounded" minOccurs="0">
|
||||
<xs:element name="member">
|
||||
<xs:complexType>
|
||||
<xs:simpleContent>
|
||||
<xs:extension base="xs:string">
|
||||
<xs:attribute type="xs:string" name="name" />
|
||||
<xs:attribute type="xs:string" name="type" />
|
||||
<xs:attribute type="xs:string" name="setter" />
|
||||
<xs:attribute type="xs:string" name="getter" />
|
||||
<xs:attribute type="xs:string" name="overrides" use="optional" />
|
||||
<xs:attribute type="xs:string" name="enum" use="optional" />
|
||||
<xs:attribute type="xs:string" name="default" use="optional" />
|
||||
</xs:extension>
|
||||
</xs:simpleContent>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
</xs:choice>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
<xs:element name="signals" minOccurs="0">
|
||||
<xs:complexType>
|
||||
<xs:sequence>
|
||||
<xs:element name="signal" maxOccurs="unbounded" minOccurs="0">
|
||||
<xs:complexType>
|
||||
<xs:sequence>
|
||||
<xs:element name="argument" maxOccurs="unbounded" minOccurs="0">
|
||||
<xs:complexType>
|
||||
<xs:sequence>
|
||||
<xs:sequence />
|
||||
</xs:sequence>
|
||||
<xs:attribute type="xs:byte" name="index" />
|
||||
<xs:attribute type="xs:string" name="name" />
|
||||
<xs:attribute type="xs:string" name="type" />
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
<xs:element type="xs:string" name="description" />
|
||||
</xs:sequence>
|
||||
<xs:attribute type="xs:string" name="name" use="optional" />
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
<xs:element name="constants" minOccurs="0">
|
||||
<xs:complexType>
|
||||
<xs:sequence>
|
||||
<xs:element name="constant" maxOccurs="unbounded" minOccurs="0">
|
||||
<xs:complexType>
|
||||
<xs:simpleContent>
|
||||
<xs:extension base="xs:string">
|
||||
<xs:attribute type="xs:string" name="name" />
|
||||
<xs:attribute type="xs:string" name="value" />
|
||||
<xs:attribute type="xs:string" name="enum" use="optional" />
|
||||
</xs:extension>
|
||||
</xs:simpleContent>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
<xs:element name="theme_items" minOccurs="0">
|
||||
<xs:complexType>
|
||||
<xs:sequence>
|
||||
<xs:element name="theme_item" maxOccurs="unbounded" minOccurs="0">
|
||||
<xs:complexType>
|
||||
<xs:simpleContent>
|
||||
<xs:extension base="xs:string">
|
||||
<xs:attribute type="xs:string" name="name" />
|
||||
<xs:attribute type="xs:string" name="data_type" />
|
||||
<xs:attribute type="xs:string" name="type" />
|
||||
<xs:attribute type="xs:string" name="default" use="optional" />
|
||||
</xs:extension>
|
||||
</xs:simpleContent>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
</xs:sequence>
|
||||
<xs:attribute type="xs:string" name="name" />
|
||||
<xs:attribute type="xs:string" name="inherits" />
|
||||
<xs:attribute type="xs:string" name="category" />
|
||||
<xs:attribute type="xs:float" name="version" />
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
</xs:schema>
|
@ -1,600 +0,0 @@
|
||||
# Entity Spell System
|
||||
|
||||
An entity and spell system for the GODOT Engine, that is usable for both 2d, and 3d games. The main purpose of this
|
||||
module is to handle spells, auras, and most entity-related things like spawning, items, inventory, containers,
|
||||
vendors, interaction, targeting, equipment (+ visuals), loot, crafting, talents, pets, npcs, etc ...
|
||||
|
||||
The module supports networking. It is designed to be authoritative, so players shouldn't be able to cheat by design.
|
||||
|
||||
It is a c++ engine module, which means you will need to compile it into godot. (See compiling)
|
||||
|
||||
## Godot Version Support
|
||||
|
||||
This module is developed with the 3.x branch of godot, usually at the newest revisions.
|
||||
|
||||
3.2 - Will likely work, probably needs changes by now. (TODO check.)\
|
||||
3.3 - Will more likely work, might need smaller changes by now. (TODO check.)\
|
||||
3.4 - Should work without any issues. (TODO check.)\
|
||||
3.x - Works.\
|
||||
4.0 - Have been fixing support from time to time. Currently it won't build on the latest master, and it will take considerable amount of work to get it to work again after the virtual method binding rework. It will be done eventually, but it will take time. [Here](https://github.com/godotengine/godot/commit/b7e10141197fdd9b0dbc4cfa7890329510d36540)'s the last know-to-work commit.
|
||||
|
||||
## Project setup tl;dr
|
||||
|
||||
### First
|
||||
|
||||
You need to create an `ESSResourceDB` resource somewhere in you project. (Or alloocate it dynamically.)
|
||||
|
||||
Now you can either:
|
||||
|
||||
-Go to `ProjectSettings->Ess->Data`, and set the `ess_resource_db_path` property also make sure that `automatic_load` is on.
|
||||
|
||||
-Or you can load it yourself and set it into the `ESS` singleton either using the `resource_db` property.
|
||||
|
||||
### Second
|
||||
|
||||
You need to add an `ESSEntitySpawner` somewhere into your SceneTree. I recommend that you create an autoload for it.
|
||||
This is where you have to implement your spawning logic.
|
||||
|
||||
### What the module doesn't cover
|
||||
|
||||
Movement, and controls.
|
||||
|
||||
Unfortunately, there isn't a one-stop-shop solution for movement, especially if we throw networking into the mix,
|
||||
and the same is true for targeting. This means that this module cannot really do much in this regard, but it will
|
||||
give you as much help to set everything up as possible.
|
||||
|
||||
## Optional Dependencies
|
||||
|
||||
Mesh Data Resource
|
||||
|
||||
https://github.com/Relintai/mesh_data_resource.git
|
||||
|
||||
Adds mesh (MeshDataResource) support to ModelVisuals.
|
||||
|
||||
## Pre-built binaries
|
||||
|
||||
You can grab a pre-built editor binary from the [Broken Seals](https://github.com/Relintai/broken_seals/releases)
|
||||
repo, should you want to. It contains all my modules.
|
||||
|
||||
## Overview
|
||||
|
||||
The module provides a huge number of script callbacks, usually as virtual methods they usually start with an underscore.
|
||||
|
||||
Do not call methods with underscores, all of them has a normal counterpart, always call that.
|
||||
|
||||
For example entity has `crafts(int id)` and `_crafts(int id)` (virtual). Always use `crafts(int id)`, it will
|
||||
call `_crafts(int id)`.
|
||||
|
||||
For networked classes, every variable is broken up into clientside, and serverside. This makes it easier to
|
||||
develop games that can also run locally, with less overhead and simpler logic.
|
||||
E.g. this makes it easy to use the visibility system even when you play locally on the server, because you just use the clientside variables,
|
||||
and your logic will work the same way.
|
||||
|
||||
## Settings
|
||||
|
||||
Entity and spell system comes with quite a few settings, you can find these under `ProjectSettings->Ess`.
|
||||
|
||||
## Singletons
|
||||
|
||||
The module contains 2 singletons. `ESS`, and `ProfileManager`. Both are accessible from scripts.
|
||||
|
||||
### The ESS singleton
|
||||
|
||||
Contains the active `ESSResourceDB` instance, and for convenience a reference to the active `ESSEntitySpawner`
|
||||
instance. Also loads/handles/processes all of the entity and spell system related ProjectSettings, so if you need
|
||||
any ESS related value from ProjectSettings, don't query it directly, get it from this singleton.
|
||||
|
||||
Customizable enums values are preprocessed, and you usually have multiple ways of getting them.
|
||||
|
||||
### The ProfileManager singleton
|
||||
|
||||
Contains methods to easily load/save/handle `PlayerProfile`s.
|
||||
|
||||
#### PlayerProfile
|
||||
|
||||
Contains player-related data, most notably `ClassProfile`s.
|
||||
|
||||
#### ClassProfile
|
||||
|
||||
Contains class-related data, most notably `ActionBarProfile`s, and `InputProfiles`.
|
||||
|
||||
#### ActionBarProfile
|
||||
|
||||
Contains the data for a set of actionbars.
|
||||
|
||||
#### InputProfileswd
|
||||
|
||||
Contains the keybind data for a class.
|
||||
|
||||
## Enums
|
||||
|
||||
ESS needs lots of enums to work, and to reduce complexity with includes they ended up in a few separate classes.
|
||||
|
||||
I'm still in the process of converting these to be customizable (`ESS` singleton / ProjectSettings).
|
||||
|
||||
(Only the ones that worth it will be converted.)
|
||||
|
||||
### EntityEnums
|
||||
|
||||
Contains Entity-related enums, like AIStates, skeleton attach points, entity flags, immunity flags,
|
||||
state flags.
|
||||
|
||||
### ItemEnums
|
||||
|
||||
Contains item-related enums, like rarity, armor type, item types.
|
||||
|
||||
### SpellEnums
|
||||
|
||||
Contains spell-related enums, like aura types, damage types, spell categories, spell types, trigger types,
|
||||
and quite a few notification constants.
|
||||
|
||||
### Customizable enums
|
||||
|
||||
Open `ProjectSettings`, and then go to `ESS/Enums`.
|
||||
|
||||
All of the string properties are customizable enums. These require a comma-separated list.
|
||||
They are essentially a godot comma separated property hint enum string.
|
||||
|
||||
They all have defaults.
|
||||
|
||||
Fer example:
|
||||
|
||||
If you want you game to work with the following stats: Agility,Intellect,Crit,Weapon Damage,Stamina
|
||||
and you want Agility,Intellect,Stamina as you main stats.
|
||||
|
||||
Firstly you want to put you main stats at the beginning, because the system will take the first `main_stat_count`
|
||||
stats as main stats.
|
||||
|
||||
And then just enter `Agility,Intellect,Stamina,Crit,Weapon Damage` into the `stats` setting, and then
|
||||
set `main_stat_count` to 3.
|
||||
|
||||
When these values are expected to be used as properties, the `ESS` singleton will create snake_cased versions automatically.
|
||||
`String stat_get_property_name(id: int) const` inside the ESS singleton for example.
|
||||
|
||||
So in the example case `ESS.stat_get_property_name(4)` would return `weapon_damage`.
|
||||
|
||||
## ESSResourceDB
|
||||
|
||||
This is a class that maps ids to resources for quick lookups.
|
||||
This is necessary in order to support networking, because you don't want to send resource paths over the network
|
||||
every time somebody casts a spell for example.
|
||||
|
||||
### ESSResourceDBStatic
|
||||
|
||||
Simple static resource db. Just drag and drop all your data that you use into it with the inspector.
|
||||
|
||||
Stores the data as vectors.
|
||||
|
||||
Supports id remapping, which means that it can assign new ids to all added resources, so they don't clash.
|
||||
The added resource's index will be set as it's id.
|
||||
|
||||
This is useful for modding support, as you can just collect every mod's resource dbs, and add them to a static db,
|
||||
and with this option enabled the ids will not clash.
|
||||
|
||||
You can see an example of this [here](https://github.com/Relintai/broken_seals/blob/master/game/scripts/game_modules/DataManager.gd).
|
||||
|
||||
### ESSResourceDBMap
|
||||
|
||||
Stores everything as a vector, and a map.
|
||||
|
||||
#### ESSResourceDBFolders
|
||||
|
||||
Inherited from `ESSResourceDBMap`.
|
||||
|
||||
It will load everything from the folders that you set up into it's `folders` property.
|
||||
|
||||
## Entity
|
||||
|
||||
This is the main class that can be used for players/mobs/npcs and also other things like chests.
|
||||
|
||||
I ended up merging subclass functionality into it, because
|
||||
that way it gains a lot more utility, by sacrificing only a small amount of memory.
|
||||
For example this way it is easy to make chests attack the player, or make spell that animate objects.
|
||||
|
||||
## Entity Body
|
||||
|
||||
Originally entities used to be inherited from `Spatial` or `Node2D`, so they could contain/drive their own models,
|
||||
but eventually on order to support both 2d, and 3d, bodies were separated from them. This unfortunately
|
||||
complicates the setup a bit, but the upsides overweight the downsides, as this way you don't need to create different
|
||||
entities for every model/body you have.
|
||||
|
||||
Bodies are stored at `EntityData->EntitySpeciesData->ModelDatas (SpeciesModelData)->Body`
|
||||
|
||||
When an `Entity` gets initialized, it will instance it's body automatically, but if you want to do it yourself,
|
||||
you can call `void instance_body(entity_data: EntityData, model_index: int)` on an `Entity`.
|
||||
|
||||
The `model_index` property tell the `Entity` which one it should use.
|
||||
|
||||
Instancing bodies does not happen immediately, but you will probably want to set an `Entity`'s position right where
|
||||
you create it, a few helpers were added:\
|
||||
|
||||
`void set_transform_2d(transform: Transform2D, only_stored: bool = false)`\
|
||||
`void set_transform_3d(transform: Transform, only_stored: bool = false)`
|
||||
|
||||
Your body implementation then can get this from an entity an set itself to the right place.
|
||||
|
||||
### CharacterSkeletons
|
||||
|
||||
CharacterSkeletons handle the looks of your characters.
|
||||
|
||||
They come in two variants, one for 2d, one for 3d.
|
||||
|
||||
They have quite a few helper methods.
|
||||
|
||||
They can store ModelVisuals, and ModelVisualEntries.
|
||||
|
||||
They support attach points. For example a character's hand.
|
||||
It adds properties based on the selected entity_type.
|
||||
These are based on the values from `ProjectSettings->ESS->Enums->skeletons_bone_attachment_points`.
|
||||
|
||||
If you want to merge meshes this is where you can implement it.
|
||||
|
||||
#### CharacterSkeleton3D
|
||||
|
||||
The 3d variant.
|
||||
|
||||
[Complex 3d skeleton scene](https://github.com/Relintai/broken_seals/blob/master/game/models/entities/human/models/armature_huf.tscn) \
|
||||
[Complex 3d skeleton script](https://github.com/Relintai/broken_seals/blob/master/game/player/CharacterSkeletonGD.gd)
|
||||
|
||||
#### CharacterSkeleton2D
|
||||
|
||||
The 2d variant.
|
||||
|
||||
[Simple 2d roguelike skeleton script](https://github.com/Relintai/broken_seals_roguelike/blob/master/game/characters/SimpleCharacter.gd) \
|
||||
[Simple 2d roguelike skeleton scene](https://github.com/Relintai/broken_seals_roguelike/blob/master/game/characters/SimpleCharacter.tscn)
|
||||
|
||||
#### ModelVisual
|
||||
|
||||
A collection ModelVisualEntries.
|
||||
|
||||
You will need to use this to define a look. For example if you have an item that will change your character's clothes,
|
||||
you will use this.
|
||||
|
||||
##### ModelVisualEntry
|
||||
|
||||
Contains meshes, textures, texture tints, mesh transforms.
|
||||
|
||||
It has 2 modes, `Bone` and `Attachment`.
|
||||
|
||||
In the bone mode, you need to select an entity type, and then a concrete bone. This is the "merge this into the final character mesh" mode.
|
||||
|
||||
In the attachment mode, you need to select a common attach point (`ProjectSettings->Ess->enums->skeletons_bone_attachment_points`),
|
||||
and the containing mesh will be put on to that point by the CharacterSkeleton. This is how you can implement weapons for example.
|
||||
|
||||
### EntitySpeciesData
|
||||
|
||||
Contains data related to a species, like `SpeciesModelData`s, and species specific spells, and auras.
|
||||
|
||||
#### SpeciesModelData
|
||||
|
||||
Contains a scene of a species's body and it's customizations.
|
||||
|
||||
The `customizable_slots_string` and `customizable_colors_string` should be filled with a comma separated string,
|
||||
they will add properties. Currently you need to click on something else, and back on the resource for these to show up,
|
||||
after a change to the strings.
|
||||
|
||||
The body can be any scene, Entity will instance it, and set it to it's body property.
|
||||
|
||||
The body should handle movement based on the player's input, it should handle sending position information through the network,
|
||||
if you want networking, it might (`CharacterSkeleton`s can also do it) also drive your animations/animation players if you have it.
|
||||
|
||||
Bodies does not need to handle the graphics themselves (`ModelVisualEntries` for example) (you can implement your logic here
|
||||
if you want to), but the `CharacterSkeleton` classes exist for that purpose.
|
||||
|
||||
[Complex 3d body script](https://github.com/Relintai/broken_seals/blob/master/game/player/Body.gd) \
|
||||
[Complex 3d body scene](https://github.com/Relintai/broken_seals/blob/master/game/models/entities/human/models/HumanFemale.tscn)
|
||||
|
||||
[Simple 2d roguelike body script](https://github.com/Relintai/broken_seals_roguelike/blob/master/game/player/Body.gd) \
|
||||
[Simple 2d roguelike body scene](https://github.com/Relintai/broken_seals_roguelike/blob/master/game/player/Body.gd)
|
||||
|
||||
#### SpeciesInstance
|
||||
|
||||
This class will store character model customization data. E.g. which hairstyle you want for an `Entity`.
|
||||
|
||||
Not yet finished!
|
||||
|
||||
### Spawning
|
||||
|
||||
Since spawning (= creating) entities is entirely dependent on the type of game you are making, ESS cannot do
|
||||
everything for you. It will set up stats, equipment etc, but there is no way to set up positions for example.
|
||||
|
||||
You can implement your spawning logic by inheriting from `ESSEntitySpawner`, and implementing `_request_entity_spawn`.
|
||||
|
||||
You should only have one spawner at any given time. It will register itself into the ESS singleton automatically.
|
||||
|
||||
Since it is inherited from Node, I recommend that you create an autoload for it.
|
||||
|
||||
The ESS singleton also contains convenience methods to request spawning an Entity.
|
||||
|
||||
[Sample 3d spawner implementation](https://github.com/Relintai/broken_seals/blob/master/game/player/bs_entity_spawner.gd) \
|
||||
[Sample 2d spawner implementation](https://github.com/Relintai/broken_seals_roguelike/blob/master/game/player/bs_entity_spawner.gd)
|
||||
|
||||
#### EntityCreateInfo
|
||||
|
||||
Entity spawning usually requires a lot of complexity and hassle, this helper class aims to make it painless.
|
||||
|
||||
All methods that deal with spawning will take this as a parameter.
|
||||
|
||||
### EntityData
|
||||
|
||||
Since setting up Entities as scenes is usually quite the hassle, `EntityData` had to be created.
|
||||
|
||||
It stores everything an entity needs.
|
||||
|
||||
In order to spawn an entity you need it.
|
||||
|
||||
#### EntityClassData
|
||||
|
||||
`EntityClassData` holds class-related information, like specs (`CharacterSpec`), spells, start spells, start auras,
|
||||
alternative ais, `EntityResource`s (mana for example).
|
||||
|
||||
#### CharacterSpec
|
||||
|
||||
`CharacterSpec` holds spec-related information, most notably talents.
|
||||
|
||||
#### EntityResource
|
||||
|
||||
EntityResources are things like mana, health, or speed. These add greater flexibility over stats.
|
||||
|
||||
The resource system is quite flexible, if you add a resource serverside, it will be automatically
|
||||
added clientside. (You have to add `EntityResource`s to the active `ESSResourceDB`!)
|
||||
|
||||
By default all entities have the build in speed and health resources, as a set index
|
||||
(`EntityEnums::ENTITY_RESOURCE_INDEX_HEALTH` and `EntityEnums::ENTITY_RESOURCE_INDEX_SPEED`).
|
||||
|
||||
There is also the `EntityEnums::ENTITY_RESOURCE_INDEX_RESOURCES_BEGIN` constant, so you have
|
||||
the current offset where the custom resources start.
|
||||
|
||||
Entity allocates these in it's `_initialize()` virtual method, if you want to customize them.
|
||||
|
||||
Note that `EntityClassData` contains an array, so you can add more resources easily to classes,
|
||||
these will be automatically added when the system initializes an `Entity`.
|
||||
|
||||
##### EntityResourceHealth
|
||||
|
||||
The standard health resource implementation.
|
||||
|
||||
##### EntityResourceSpeed
|
||||
|
||||
The standard speed resource implementation.
|
||||
|
||||
#### EntityResourceCostData
|
||||
|
||||
This is the class that lets you implement resource costs. For example mana cost for a spell.
|
||||
|
||||
##### EntityResourceCostDataResource
|
||||
|
||||
The standard resource cost implementation.
|
||||
|
||||
##### EntityResourceCostDataHealth
|
||||
|
||||
The standard health resource cost implementation.
|
||||
|
||||
It has a resource property, so you can just assign any resource to this.
|
||||
|
||||
### Interactions
|
||||
|
||||
If you want your player to interact with it's target. For example a vendor, or loot.
|
||||
|
||||
First make sure that you can interact, by checking `Entity.cans_interact()` or `Entity.canc_interact()`.
|
||||
If this returns true, you can call `Entity.crequest_interact()`. This will call `Entity.sinteract()` serverside.
|
||||
|
||||
Interactions are handled by `EntityData` by default. It has the same methods. If `EntityData` is not set, the `Entity`
|
||||
will try to call the same overridable on itself.
|
||||
|
||||
You can see an example implementation [here](https://github.com/Relintai/broken_seals/blob/master/game/scripts/entities/EntityDataGD.gd).
|
||||
|
||||
### AI
|
||||
|
||||
You can implement ai by extending `EntityAI`, and then assigning it to an `EntityData`.
|
||||
|
||||
When an `Entity` gets spawned it will create a copy for itself, so you can safely use class variables.
|
||||
|
||||
#### AIFormation
|
||||
|
||||
Not yet finished, it was meant as a way to calculate offset pet positions, (If an `Entity` has let's say
|
||||
4 pets you don't just want them all to stay on top of their controller).
|
||||
|
||||
If this functionality ends up in `EntityAI`, after pets are finished, this will be removed.
|
||||
|
||||
### Pets
|
||||
|
||||
Unfortunately pet support is not yet finished.
|
||||
|
||||
It is mostly done though, so you will see pet-related methods scattered around.
|
||||
|
||||
### Bags
|
||||
|
||||
Stores items. See `Bag`. It has quite a few virtual methods, you should be able to implement most inventory types
|
||||
should you want to.
|
||||
|
||||
Entity will send these over the network.
|
||||
|
||||
Also Entities have a target bag property. For example this makes vendors easily implementable.
|
||||
|
||||
### VRPCs
|
||||
|
||||
Entities has a networked visibility system. The method itself is called `vrpc`, it works the same way as normal rpcs.
|
||||
If you want to send data to every client that sees the current entity, use this.
|
||||
|
||||
## Spells, Auras, Talents
|
||||
|
||||
Spell is the class you need to create both spells, and aura.\
|
||||
It stores the data, and also it has the ability to be scripted.\
|
||||
Talents are actually just spells used as Auras. Right now the system just applies them as a permanent aura.\
|
||||
You don't need to worry about applying auras, cast them as spells instead. It they are set to permanent, or they have a duration set they will be applied as an aura automatically.
|
||||
|
||||
Talent ranks are implemented by deapplying the earlier rank first, then applying the new rank.
|
||||
|
||||
### How to
|
||||
|
||||
Request casting a spell clientside: `void spell_crequest_cast(spell_id: int)` \
|
||||
Request to learn a spell clientside: `void spell_learn_requestc(id: int)`
|
||||
|
||||
Request talent learning clientside: \
|
||||
`void character_talent_crequest_learn(spec_index: int, character_talent_row: int, character_talent_culomn: int)` or \
|
||||
`void class_talent_crequest_learn(spec_index: int, class_talent_row: int, class_talent_culomn: int)`
|
||||
|
||||
#### Cast a spell
|
||||
|
||||
Note that you should only do this serverside.
|
||||
|
||||
```
|
||||
# Or get it from the active ESSResourceDB, etc
|
||||
export(Spell) var spell : Spell
|
||||
|
||||
func scast_spell() -> void:
|
||||
var sci : SpellCastInfo = SpellCastInfo.new()
|
||||
|
||||
sci.caster = info.caster
|
||||
sci.target = info.target
|
||||
sci.has_cast_time = spell.cast_enabled
|
||||
sci.cast_time = spell.cast_cast_time
|
||||
sci.spell_scale = info.spell_scale
|
||||
sci.set_spell(spell)
|
||||
|
||||
spell.cast_starts(sci)
|
||||
|
||||
```
|
||||
|
||||
#### Apply an aura
|
||||
|
||||
Normally you shouldn't do this, this is for more advanced uses. Cast the aura as a spell instead.
|
||||
|
||||
Note that you should only apply auras serverside, they will be sent to clients automatically.
|
||||
|
||||
```
|
||||
# Or get it from the active ESSResourceDB, etc
|
||||
export(Spell) var aura : Spell
|
||||
|
||||
func sapply_aura() -> void:
|
||||
var ainfo : AuraApplyInfo = AuraApplyInfo.new()
|
||||
|
||||
ainfo.caster = info.caster
|
||||
ainfo.target = info.caster
|
||||
ainfo.spell_scale = 1
|
||||
ainfo.aura = aura
|
||||
|
||||
aura.sapply(ainfo)
|
||||
|
||||
```
|
||||
|
||||
#### UI
|
||||
|
||||
[Complete UI Implementation](https://github.com/Relintai/broken_seals/tree/master/game/ui/player)
|
||||
|
||||
[Player UI Core Implementation](https://github.com/Relintai/broken_seals/tree/master/game/ui/player/player_ui)
|
||||
|
||||
[Aura Frame Implementation](https://github.com/Relintai/broken_seals/tree/master/game/ui/player/auraframe) \
|
||||
[Castbar Implementation](https://github.com/Relintai/broken_seals/tree/master/game/ui/player/castbar) \
|
||||
[Unitframe Implementation](https://github.com/Relintai/broken_seals/tree/master/game/ui/player/unitframes)
|
||||
|
||||
[Actionbar Implementation](https://github.com/Relintai/broken_seals/tree/master/game/ui/player/actionbars)
|
||||
|
||||
[Character Window Implementation](https://github.com/Relintai/broken_seals/tree/master/game/ui/player/character) \
|
||||
[Inventory Window Implementation](https://github.com/Relintai/broken_seals/tree/master/game/ui/player/bags) \
|
||||
[Crafting Window Implementation](https://github.com/Relintai/broken_seals/tree/master/game/ui/player/crafting) \
|
||||
[Loot Window Implementation](https://github.com/Relintai/broken_seals/tree/master/game/ui/player/loot_window) \
|
||||
[Talent Window Implementation](https://github.com/Relintai/broken_seals/tree/master/game/ui/player/talents) \
|
||||
[Spellbook Window Implementation](https://github.com/Relintai/broken_seals/tree/master/game/ui/player/spellbook) \
|
||||
[Vendor Window Implementation](https://github.com/Relintai/broken_seals/tree/master/game/ui/player/vendor_window) \
|
||||
[Trainer Window Implementation](https://github.com/Relintai/broken_seals/tree/master/game/ui/player/trainer)
|
||||
|
||||
[3D Nameplate Implementation](https://github.com/Relintai/broken_seals/tree/master/game/ui/world/nameplates) \
|
||||
[2D Nameplate Implementation](https://github.com/Relintai/broken_seals_roguelike/tree/master/game/ui/nameplates)
|
||||
|
||||
### Infos / Pipelines
|
||||
|
||||
#### SpellCastInfo
|
||||
|
||||
Stores information about the state of a spell's cast.
|
||||
|
||||
#### AuraApplyInfo
|
||||
|
||||
Helps to apply auras
|
||||
|
||||
#### SpellDamageInfo, SpellHealInfo
|
||||
|
||||
These are used in the damage and heal calculation. For example these can be used to implement immunities, or absorb effects
|
||||
by modifying their damage values in aura callbacks.
|
||||
|
||||
### Projectiles
|
||||
|
||||
Spells support projectiles, they are created/set up inside `void handle_projectile(info: SpellCastInfo)`.
|
||||
|
||||
The default implementation will instance `Spell`'s projectile scene (if set), and then it will try to call a
|
||||
`void setup_projectile(info: SpellCastInfo)` on it if exists.
|
||||
|
||||
You can override this behaviour by implementing your own `_void handle_projectile(info: SpellCastInfo)` on `Spell`
|
||||
|
||||
Note that the module already adds `SpellFollowProjectile3D`, but this has not been finished yet.
|
||||
|
||||
## Items
|
||||
|
||||
Items are implemented using 2 classes, `ItemTemplate`, and `ItemInstance`.
|
||||
|
||||
`ItemTemplate` contains all information for a potential item. You can generate `Iteminstance`s with this,
|
||||
using it's `ItemInstance create_item_instance()` method. You can also implement your custom item creation logic
|
||||
using the `void _create_item_instance()` virtual.
|
||||
|
||||
`ItemInstance` is the actual item.
|
||||
|
||||
### Loot
|
||||
|
||||
Looting can be implemented using `Entity`'s target bag functionality.
|
||||
|
||||
You can see an example implementation [here](https://github.com/Relintai/broken_seals/blob/master/game/scripts/entities/EntityDataGD.gd). \
|
||||
And an example ui implementation [here](https://github.com/Relintai/broken_seals/tree/master/game/ui/player/loot_window).
|
||||
|
||||
## XP
|
||||
|
||||
You can set all the xp values for your levels in `ProjectSettings->Ess->xp`.
|
||||
|
||||
Now you can start distributing xp, for whatever you'd like to Entities, using `Entity.xp_adds(value : int)`
|
||||
|
||||
## Examples
|
||||
|
||||
Eventually I'll create a separate repository with a few examples/demos, but for now you can check the game
|
||||
I've been working on for examples.
|
||||
|
||||
3d:
|
||||
https://github.com/Relintai/broken_seals.git
|
||||
|
||||
2d turn based:
|
||||
https://github.com/Relintai/broken_seals_roguelike
|
||||
|
||||
2d:
|
||||
https://github.com/Relintai/broken_seals_2d.git
|
||||
|
||||
## Compiling
|
||||
|
||||
First make sure that you can compile godot. See the official docs: https://docs.godotengine.org/en/3.x/development/compiling/index.html
|
||||
|
||||
1. Clone the engine if you haven't already:
|
||||
|
||||
If you want Godot 3.x:
|
||||
```git clone -b 3.x https://github.com/godotengine/godot.git godot```
|
||||
|
||||
If you want Godot 4.0:
|
||||
```git clone https://github.com/godotengine/godot.git godot```
|
||||
|
||||
2. go into the modules folder inside the engine's directory"
|
||||
|
||||
```cd godot```
|
||||
```cd modules```
|
||||
|
||||
3. clone this repository
|
||||
|
||||
```git clone https://github.com/Relintai/entity_spell_system.git entity_spell_system```
|
||||
|
||||
(the folder needs to be named entity_spell_system!)
|
||||
|
||||
4. Go up one folder
|
||||
|
||||
```cd ..```
|
||||
|
||||
5. Compile godot.
|
||||
|
||||
For example:
|
||||
|
||||
```scons p=x11 t=release_debug tools=yes```
|
@ -1,69 +0,0 @@
|
||||
# Mesh data resource Module
|
||||
|
||||
A c++ Godot engine module, that adds a resource, which contains raw mesh data for merging and collider information.
|
||||
|
||||
The module also comes with importers (gltf, and collada for now), you can import 3d models as MeshDataResources with these.
|
||||
|
||||
## Optional Dependencies
|
||||
|
||||
`https://github.com/Relintai/props`: If present, you also get a prop importer for MeshDataInstances.
|
||||
`https://github.com/Relintai/mesh_utils`: If present, you get mesh simplification/optimization options at import.
|
||||
|
||||
## Pre-built binaries
|
||||
|
||||
You can grab a pre-built editor binary from the [Broken Seals](https://github.com/Relintai/broken_seals/releases)
|
||||
repo, should you want to. It contains all my modules.
|
||||
|
||||
## MeshDataResource
|
||||
|
||||
The resource that holds mesh and collider data.
|
||||
|
||||
## MeshDataResourceCollection
|
||||
|
||||
Holds a list of MeshDataResources.
|
||||
|
||||
## MeshDataInstance
|
||||
|
||||
You can easily put MeshDataResources into the scene with these. They are equivalent to MeshInstances, except they work
|
||||
with MeshDataResources.
|
||||
|
||||
## Importers
|
||||
|
||||
In order to import a 3d model as a MeshDataResource, select the model, go to the import tab, and switch the import type to `<type> MDR`. Like:
|
||||
|
||||
![Import Tab](screenshots/import.png)
|
||||
|
||||
If you set the import type to single, the importers will convert the first model that they encounter into a MeshDataResource, then save that,
|
||||
if you set it to multiple, you get a MeshDataResourceCollection as the main resource, and also all encountered models as files separately.
|
||||
|
||||
Since MeshDataResource can hold collider information, these importers can create this for you. There are quite a few options for it:
|
||||
|
||||
![Colliders](screenshots/import_2.png)
|
||||
|
||||
## Building
|
||||
|
||||
1. Get the source code for the engine.
|
||||
|
||||
If you want Godot 3.2:
|
||||
```git clone -b 3.2 https://github.com/godotengine/godot.git godot```
|
||||
|
||||
If you want Godot 4.0:
|
||||
```git clone https://github.com/godotengine/godot.git godot```
|
||||
|
||||
[last tested commit for 4.0](https://github.com/godotengine/godot/commit/b7e10141197fdd9b0dbc4cfa7890329510d36540)
|
||||
|
||||
2. Go into Godot's modules directory.
|
||||
|
||||
```
|
||||
cd ./godot/modules/
|
||||
```
|
||||
|
||||
3. Clone this repository
|
||||
|
||||
```
|
||||
git clone https://github.com/Relintai/mesh_data_resource mesh_data_resource
|
||||
```
|
||||
|
||||
4. Build Godot. [Tutorial](https://docs.godotengine.org/en/latest/development/compiling/index.html)
|
||||
|
||||
|
@ -1,60 +0,0 @@
|
||||
# Mesh Utils Module
|
||||
|
||||
This is a c++ engine module for the Godot engine, containing my mesh merging utilities.
|
||||
|
||||
It supports both godot 3.2 and 4.0 (master [last tested commit](https://github.com/godotengine/godot/commit/b7e10141197fdd9b0dbc4cfa7890329510d36540)). Note that since 4.0 is still in very early stages I only
|
||||
check whether it works from time to time.
|
||||
|
||||
# Pre-built binaries
|
||||
|
||||
You can grab a pre-built editor binary from the [Broken Seals](https://github.com/Relintai/broken_seals/releases)
|
||||
repo, should you want to. It contains all my modules.
|
||||
|
||||
# Optional Dependencies
|
||||
|
||||
[Mesh Data Resource](https://github.com/Relintai/mesh_data_resource): Support for merged meshes, even in gles2.
|
||||
Adds MeshMerger a few helper methods.
|
||||
|
||||
# MeshUtils Singleton
|
||||
|
||||
Contains generic algorithms that manipulate meshes.
|
||||
|
||||
# Mesh Merger
|
||||
|
||||
Works similarly to SurfaceTool, but it has more utility functions.
|
||||
|
||||
# Fast Quadratic Mesh Simplifier
|
||||
|
||||
A port of https://github.com/Whinarn/UnityMeshSimplifier .
|
||||
For future reference it's based on e8ff4e8862735197c3308cfe926eeba68e0d2edb.
|
||||
Porting is mostly done, but it does needs some debugging (it has a crash if smart linking is enabled).
|
||||
|
||||
I might just return to using the original FQMS. As if meshes are merged together using `MeshUtils.merge_mesh_array`, or
|
||||
`bake_mesh_array_uv` the original algorithm will work fine. Still on the fence about it.
|
||||
|
||||
# Building
|
||||
|
||||
1. Get the source code for the engine.
|
||||
|
||||
If you want Godot 3.2:
|
||||
```git clone -b 3.2 https://github.com/godotengine/godot.git godot```
|
||||
|
||||
If you want Godot 4.0:
|
||||
```git clone https://github.com/godotengine/godot.git godot```
|
||||
|
||||
|
||||
2. Go into Godot's modules directory.
|
||||
|
||||
```
|
||||
cd ./godot/modules/
|
||||
```
|
||||
|
||||
3. Clone this repository
|
||||
|
||||
```
|
||||
git clone https://github.com/Relintai/mesh_utils mesh_utils
|
||||
```
|
||||
|
||||
4. Build Godot. [Tutorial](https://docs.godotengine.org/en/latest/development/compiling/index.html)
|
||||
|
||||
|
@ -1,95 +0,0 @@
|
||||
# Props Module
|
||||
|
||||
This is a c++ engine module for the Godot Engine.
|
||||
|
||||
It gives you props, and editor utilities to convert scenes to props.
|
||||
|
||||
It supports both godot 3.2 and 4.0 (master [last tested commit](https://github.com/godotengine/godot/commit/b7e10141197fdd9b0dbc4cfa7890329510d36540)). Note that since 4.0 is still in very early stages I only
|
||||
check whether it works from time to time.
|
||||
|
||||
# Pre-built binaries
|
||||
|
||||
You can grab a pre-built editor binary from the [Broken Seals](https://github.com/Relintai/broken_seals/releases)
|
||||
repo, should you want to. It contains all my modules.
|
||||
|
||||
# Optional Dependencies
|
||||
|
||||
[Mesh Data Resource](https://github.com/Relintai/mesh_data_resource): Support for merged meshes, even in gles2.\
|
||||
[Texture Packer](https://github.com/Relintai/texture_packer): Prop Instance will use this to merge textures.
|
||||
|
||||
# PropData
|
||||
|
||||
Props are basically 3D scenes in a simple format, so other things can easily process them without instancing.
|
||||
|
||||
For example if you create a building from MeshDataInstances, and then convert that scene to a prop, Voxelman
|
||||
can spawn it, merge it's meshes, and create lods without any scene instancing.
|
||||
|
||||
PropData is the main class you'll use, it's main purpose it to store a list of PropDataEntries.
|
||||
|
||||
# PropDataEntries
|
||||
|
||||
These are the classes that actually store data.
|
||||
|
||||
They contain 4 methods for scene->prop conversion, namely:
|
||||
|
||||
```
|
||||
//Whether or not this PropDataEntry can process the given Node.
|
||||
virtual bool _processor_handles(Node *node);
|
||||
|
||||
//Save the given Node into the given prop_data any way you like, at transform.
|
||||
virtual void _processor_process(Ref<PropData> prop_data, Node *node, const Transform &transform);
|
||||
|
||||
//Turn PropDataEntry back into a Node
|
||||
virtual Node *_processor_get_node_for(const Transform &transform);
|
||||
|
||||
//Whether the system should skip evaluating the children of a processes Node or not. See PropDataScene, or PropDataProp.
|
||||
virtual bool _processor_evaluate_children();
|
||||
```
|
||||
|
||||
# PropInstances
|
||||
|
||||
PropInstances are not yet finished.
|
||||
|
||||
They will be able to merge meshes, texture etc from a Prop, and also generate lods for it.
|
||||
|
||||
Essentially they will be a more advanced MeshInstance.
|
||||
|
||||
Voxelman implements all of this, just haven't finished porting it yet.
|
||||
|
||||
# PropUtils Singleton
|
||||
|
||||
The PropUtils singleton helps with scene->prop conversion.
|
||||
|
||||
You can register new PropDataEntries as processors, should you need to.
|
||||
|
||||
# Scene conversion
|
||||
|
||||
You can either click the new "To Prop" button on the menubar of the 3D scene for a quick conversion,
|
||||
or look into Project->Tools.
|
||||
|
||||
# Building
|
||||
|
||||
1. Get the source code for the engine.
|
||||
|
||||
If you want Godot 3.2:
|
||||
```git clone -b 3.2 https://github.com/godotengine/godot.git godot```
|
||||
|
||||
If you want Godot 4.0:
|
||||
```git clone https://github.com/godotengine/godot.git godot```
|
||||
|
||||
|
||||
2. Go into Godot's modules directory.
|
||||
|
||||
```
|
||||
cd ./godot/modules/
|
||||
```
|
||||
|
||||
3. Clone this repository
|
||||
|
||||
```
|
||||
git clone https://github.com/Relintai/props props
|
||||
```
|
||||
|
||||
4. Build Godot. [Tutorial](https://docs.godotengine.org/en/latest/development/compiling/index.html)
|
||||
|
||||
|
@ -1,95 +0,0 @@
|
||||
# Prop2Ds Module
|
||||
|
||||
This is a c++ engine module for the Godot Engine.
|
||||
|
||||
It gives you props, and editor utilities to convert scenes to props.
|
||||
|
||||
It supports both godot 3.2 and 4.0 (master [last tested commit](https://github.com/godotengine/godot/commit/b7e10141197fdd9b0dbc4cfa7890329510d36540)). Note that since 4.0 is still in very early stages I only
|
||||
check whether it works from time to time.
|
||||
|
||||
# Pre-built binaries
|
||||
|
||||
You can grab a pre-built editor binary from the [Broken Seals](https://github.com/Relintai/broken_seals/releases)
|
||||
repo, should you want to. It contains all my modules.
|
||||
|
||||
# Optional Dependencies
|
||||
|
||||
[Mesh Data Resource](https://github.com/Relintai/mesh_data_resource): Support for merged meshes, even in gles2.\
|
||||
[Texture Packer](https://github.com/Relintai/texture_packer): Prop2D Instance will use this to merge textures.
|
||||
|
||||
# Prop2DData
|
||||
|
||||
Prop2Ds are basically 3D scenes in a simple format, so other things can easily process them without instancing.
|
||||
|
||||
For example if you create a building from MeshDataInstances, and then convert that scene to a prop, Voxelman
|
||||
can spawn it, merge it's meshes, and create lods without any scene instancing.
|
||||
|
||||
Prop2DData is the main class you'll use, it's main purpose it to store a list of Prop2DDataEntries.
|
||||
|
||||
# Prop2DDataEntries
|
||||
|
||||
These are the classes that actually store data.
|
||||
|
||||
They contain 4 methods for scene->prop conversion, namely:
|
||||
|
||||
```
|
||||
//Whether or not this Prop2DDataEntry can process the given Node.
|
||||
virtual bool _processor_handles(Node *node);
|
||||
|
||||
//Save the given Node into the given prop_data any way you like, at transform.
|
||||
virtual void _processor_process(Ref<Prop2DData> prop_data, Node *node, const Transform &transform);
|
||||
|
||||
//Turn Prop2DDataEntry back into a Node
|
||||
virtual Node *_processor_get_node_for(const Transform &transform);
|
||||
|
||||
//Whether the system should skip evaluating the children of a processes Node or not. See Prop2DDataScene, or Prop2DDataProp2D.
|
||||
virtual bool _processor_evaluate_children();
|
||||
```
|
||||
|
||||
# Prop2DInstances
|
||||
|
||||
Prop2DInstances are not yet finished.
|
||||
|
||||
They will be able to merge meshes, texture etc from a Prop2D, and also generate lods for it.
|
||||
|
||||
Essentially they will be a more advanced MeshInstance.
|
||||
|
||||
Voxelman implements all of this, just haven't finished porting it yet.
|
||||
|
||||
# Prop2DUtils Singleton
|
||||
|
||||
The Prop2DUtils singleton helps with scene->prop conversion.
|
||||
|
||||
You can register new Prop2DDataEntries as processors, should you need to.
|
||||
|
||||
# Scene conversion
|
||||
|
||||
You can either click the new "To Prop2D" button on the menubar of the 3D scene for a quick conversion,
|
||||
or look into Project->Tools.
|
||||
|
||||
# Building
|
||||
|
||||
1. Get the source code for the engine.
|
||||
|
||||
If you want Godot 3.2:
|
||||
```git clone -b 3.2 https://github.com/godotengine/godot.git godot```
|
||||
|
||||
If you want Godot 4.0:
|
||||
```git clone https://github.com/godotengine/godot.git godot```
|
||||
|
||||
|
||||
2. Go into Godot's modules directory.
|
||||
|
||||
```
|
||||
cd ./godot/modules/
|
||||
```
|
||||
|
||||
3. Clone this repository
|
||||
|
||||
```
|
||||
git clone https://github.com/Relintai/props props
|
||||
```
|
||||
|
||||
4. Build Godot. [Tutorial](https://docs.godotengine.org/en/latest/development/compiling/index.html)
|
||||
|
||||
|
@ -1,25 +0,0 @@
|
||||
# TileMap
|
||||
|
||||
Godot's TileMap but as an engine module, with a few smaller features added.
|
||||
|
||||
The tilemap classes will be prefixed with R, so it compiles cleanly with the built in TileMap class.
|
||||
|
||||
# Building
|
||||
|
||||
1. Get the source code for the engine.
|
||||
|
||||
```git clone -b 3.x https://github.com/godotengine/godot.git godot```
|
||||
|
||||
2. Go into Godot's modules directory.
|
||||
|
||||
```
|
||||
cd ./godot/modules/
|
||||
```
|
||||
|
||||
3. Clone this repository
|
||||
|
||||
```
|
||||
git clone https://github.com/Relintai/rtile_map.git rtile_map
|
||||
```
|
||||
|
||||
4. Build Godot. [Tutorial](https://docs.godotengine.org/en/latest/development/compiling/index.html)
|
@ -1,28 +0,0 @@
|
||||
# Skeleton Editor
|
||||
|
||||
This is a c++ engine module for the Godot engine that contains a modularized version of TokageItLab's pr's 3.2 version from the godot engine repository, until it gets merged.
|
||||
|
||||
The original pr is here: https://github.com/godotengine/godot/pull/45699
|
||||
Tht 3.x version (linked in the pr itself) is here (This is the base for this module): https://github.com/TokageItLab/godot/tree/pose-edit-mode
|
||||
|
||||
I'm developing this for godot 3.x, it will probably work on earlier versions though. 4.0 is not supported.
|
||||
|
||||
# Building
|
||||
|
||||
1. Get the source code for the engine.
|
||||
|
||||
```git clone -b 3.x https://github.com/godotengine/godot.git godot```
|
||||
|
||||
2. Go into Godot's modules directory.
|
||||
|
||||
```
|
||||
cd ./godot/modules/
|
||||
```
|
||||
|
||||
3. Clone this repository
|
||||
|
||||
```
|
||||
git clone https://github.com/Relintai/skeleton_editor skeleton_editor
|
||||
```
|
||||
|
||||
4. Build Godot. [Tutorial](https://docs.godotengine.org/en/latest/development/compiling/index.html)
|
@ -1,220 +0,0 @@
|
||||
# Terraman
|
||||
|
||||
A terrain engine for godot, focusing more on editor integration, gameplay-related features, and extendability (even from gdscript), without sacrificing too much speed.
|
||||
|
||||
It is a spinoff of [Voxelman](https://github.com/Relintai/voxelman). I started working on it when I realized that not only a full 3d voxel engine is too hard for me to use properly for an rpg (just think about how hard it is to do smooth zone - zone and dungeon transitions with the proper fidelity for an rpg), it's also unnecessary.
|
||||
|
||||
I could have technically implemented all of this into voxelman, as having only have one row of chunks, and then setting chunk height to 1, and creating a mesher that reads isolevel values as a normal height map will achieve the same effect. However as voxelman has lots of features with noises, lights and vertices, adding this on top of that module would have ended up being messy just for this reason alone (and also let's not forget the 3d apis).
|
||||
|
||||
So I ended up creating this. Everything works the same as in voxelman, but the apis have been simplified to make UX a bit better.
|
||||
|
||||
This is an engine module! Which means that you will need to compile it into Godot! [See the compiling section here.](#compiling)
|
||||
|
||||
You can grab pre-built binaries (even editor + export templates) from the [Broken Seals](https://github.com/Relintai/broken_seals/releases) repo.
|
||||
|
||||
## Godot Version Support
|
||||
|
||||
3.2 - Will likely work, probably needs changes by now. (TODO check.)\
|
||||
3.3 - Will more likely work, might need smaller changes by now. (TODO check.)\
|
||||
3.4 - Should work without any issues. (TODO check.)\
|
||||
3.x - Works.\
|
||||
4.0 - Have been fixing support from time to time. Currently it won't build. Mostly done with the fix though.
|
||||
|
||||
## Optional Dependencies
|
||||
|
||||
`https://github.com/Relintai/texture_packer`: You get access to [TerraLibraryMerger](#voxellibrarymerger) and [TerraLibraryMergerPCM](#voxellibrarymergerpcm). \
|
||||
`https://github.com/Relintai/mesh_data_resource`: You get access to a bunch of properties, and methods that can manipulate meshes.\
|
||||
`https://github.com/Relintai/props`: You get access to a bunch of properties, and methods that can manipulate, and use props.\
|
||||
`https://github.com/Relintai/mesh_utils`: Lets you use lod levels higher than 4 by default.
|
||||
|
||||
## Usage
|
||||
|
||||
First create a scene, and add a TerraWorldBlocky node into it. Create a TerraLibrary, and assign it to the Library property.
|
||||
Also, add a TerraSurface into your library.
|
||||
|
||||
Tick the editable property, deselect, then select the world again, and click the insert button at the top toolbar, or press B to insert a
|
||||
voxel at the inspector's camera's location.
|
||||
|
||||
Select the add button, and now you can just add voxels with the mouse, by clicking on the newly added voxel.
|
||||
|
||||
## TerraLibrary
|
||||
|
||||
This class stores the materials, and the TerraSurfaces.
|
||||
|
||||
Lod levels will automatically try to use materials of their own index.\
|
||||
For example lod level 1 will try to use material index 1, lod level 2 will try to use material index 2, etc.\
|
||||
If a material index is not available, they'll use the highest that is.\
|
||||
For example lod level 5 will try to get material index 5, but if you only have 3 materials it will use the 3rd.
|
||||
|
||||
### TerraLibrarySimple
|
||||
|
||||
The simplest library, just assign a material with a texture, and using the atlas_rows and atlas_culomns properties to tell the system
|
||||
how the UVs should be divided.
|
||||
|
||||
This is the basic Minecraft-style lib rary. Use this if you just have one texture atlas.
|
||||
|
||||
### TerraLibraryMerger
|
||||
|
||||
You will only have this if your godot also contains https://github.com/Relintai/texture_packer
|
||||
|
||||
You can assign any texture to your surfaces with this, and it will merge them together.
|
||||
|
||||
### TerraLibraryMergerPCM
|
||||
|
||||
(PCM = Per Chunk Material)
|
||||
|
||||
You will only have this if your godot also contains https://github.com/Relintai/texture_packer
|
||||
|
||||
You can assign any texture to your surfaces with this, and it will merge them together, but it will do it for every required chunk/voxel combination.
|
||||
|
||||
For example if you have a chunk with voxel Grass, and voxel Stone used in it, this library will create a material with a merged texture for Stone and Grass.
|
||||
If you have an anouther chunk which only has Grass and Stone in it, this material will be reused.
|
||||
And if you have a third chunk which only has a Grass voxel used in it, it will get a new merged material and texture only containing Grass voxel.
|
||||
|
||||
## Worlds
|
||||
|
||||
The 2 base classes. These won't do meshing on their own:
|
||||
|
||||
TerraWorld: Basic world, does not do anything until you implemnent the required virtual methods!\
|
||||
TerraWorldDefault: This adds threading, and LoD storage support to TerraWorld. Will not create meshes for you!
|
||||
|
||||
### TerraWorldBlocky
|
||||
|
||||
It generated UV mapped standard simple terrain meshes.
|
||||
The default algorithm can also generate normal lods.
|
||||
|
||||
### Level generation
|
||||
|
||||
Assign a TerraManLevelGenerator to the `World`'s `Level Generator` property.
|
||||
|
||||
You can write your own algorithm by implementing the ``` void _generate_chunk(chunk: TerraChunk) virtual ``` method.
|
||||
|
||||
`TerraManLevelGeneratorFlat` is also available, it will generate a floor for you, if you use it.
|
||||
|
||||
## TerraJobs
|
||||
|
||||
Producing just a terrain mesh for a chunk is not that hard by itself. However when you start adding layers/features
|
||||
like lod generation, collision meshes (especially since manipulating the physics server is not threadsafe),
|
||||
vertex lights, props, snapping props, props with vertex lights, etc
|
||||
chunk mesh generation can quickly become a serious mess.
|
||||
|
||||
TerraJobs are meant to solve the issue with less complexity.
|
||||
|
||||
They also provide a way to easily modularize mesh and lod generation.
|
||||
|
||||
### TerraJob
|
||||
|
||||
Base class for jobs.
|
||||
|
||||
This is inherited from `ThreadPoolJob`.
|
||||
|
||||
A job has a reference to it's owner chunk.
|
||||
|
||||
If you implement your own jobs, when your job finishes call `next_job()`.
|
||||
|
||||
### TerraLightJob
|
||||
|
||||
This is the job that will generate vertex light based ao, random ao, and will bake your `TerraLight`s.
|
||||
|
||||
### TerraTerrainJob
|
||||
|
||||
This will generate your terrain collider and mesh (with lods) for you, using the meshers that you add into it.
|
||||
|
||||
Your lod setup is easily customizable with [TerraMesherJobSteps](https://github.com/Relintai/terraman/blob/master/world/jobs/voxel_mesher_job_step.h). The setup happens in your selected world's `_create_chunk` method.
|
||||
|
||||
### TerraPropJob
|
||||
|
||||
This will generate your prop meshes (with lods).
|
||||
|
||||
Also supports [TerraMesherJobSteps](https://github.com/Relintai/terraman/blob/master/world/jobs/voxel_mesher_job_step.h).
|
||||
|
||||
### Internal workings
|
||||
|
||||
#### TerraWorld
|
||||
|
||||
Whenever you want to spawn a chunk your World will create it using the ``` TerraChunk _create_chunk(x: int, y: int, z: int, chunk: TerraChunk) virtual ``` method.
|
||||
|
||||
Since properly initializing a chunk usually takes quite a few steps that you probably don't want to repeat everywhere the `chunk`
|
||||
parameter was added. This means you can just call the super `_create_chunk` methods, and you won't need to worry about your chunk
|
||||
getting overridden. Like:
|
||||
|
||||
Note that `_create_chunk` is also responsible for initializing chunks if you have them stored inside a scene.
|
||||
This is done by `setup_chunk(shunk)` in `TerraWorld`.
|
||||
|
||||
```
|
||||
func _create_chunk(x : int, y : int, z : int, chunk : TerraChunk) -> TerraChunk:
|
||||
if !chunk:
|
||||
chunk = MyChunk.new()
|
||||
|
||||
# We need to check whether or not we need to initialize jobs
|
||||
if chunk.job_get_count() == 0:
|
||||
# Setup a blocky (minecratf like) mesher job
|
||||
var tj : TerraTerrainJob = TerraTerrainJob.new()
|
||||
|
||||
var s : TerraMesherJobStep = TerraMesherJobStep.new()
|
||||
s.job_type = TerraMesherJobStep.TYPE_NORMAL
|
||||
tj.add_jobs_step(s)
|
||||
|
||||
tj.add_mesher(TerraMesherBlocky.new())
|
||||
tj.add_liquid_mesher(TerraMesherLiquidBlocky.new())
|
||||
|
||||
chunk.job_add(tj);
|
||||
|
||||
#setup your chunk here
|
||||
|
||||
return ._create_chunk(x, y, z, chunk)
|
||||
```
|
||||
|
||||
You can look at the world implementations for more examples: [TerraWorldBlocky](https://github.com/Relintai/terraman/blob/master/world/blocky/voxel_world_blocky.cpp).
|
||||
|
||||
#### TerraChunk
|
||||
|
||||
Stores terrain data, prop data. And mesh data (TerraChunkDefault), and the mesh generation jobs.
|
||||
|
||||
When it starts building meshes it will start submitting jobs to thread_pool (if present) one by one.
|
||||
|
||||
#### TerraMesher
|
||||
|
||||
If you want to implement your own meshing algorithm you can do so by overriding ``` void _add_chunk(chunk: TerraChunk) virtual ```.
|
||||
|
||||
TerraMesher works similarly to SurfaceTool, so first you need to set colors, uvs, etc and then call add_vertex.
|
||||
They won't get reset, so for example if you want all your vertices to have a certain color, you can get away with setting it only once.
|
||||
|
||||
## Compiling
|
||||
|
||||
First make sure that you can compile godot. See the official docs: https://docs.godotengine.org/en/3.x/development/compiling/index.html
|
||||
|
||||
1. Clone the engine if you haven't already:
|
||||
|
||||
If you want Godot 3.x:
|
||||
```git clone -b 3.x https://github.com/godotengine/godot.git godot```
|
||||
|
||||
If you want Godot 4.0:
|
||||
```git clone https://github.com/godotengine/godot.git godot```
|
||||
|
||||
|
||||
2. go into the modules folder inside the engine's directory:
|
||||
|
||||
```cd godot``` \
|
||||
```cd modules```
|
||||
|
||||
3. clone this repository
|
||||
|
||||
```git clone https://github.com/Relintai/terraman.git terraman```
|
||||
|
||||
(the folder needs to be named terraman!)
|
||||
|
||||
4. If you want the optional dependencies run these commands as well:
|
||||
|
||||
```git clone https://github.com/Relintai/texture_packer.git texture_packer``` \
|
||||
```git clone https://github.com/Relintai/mesh_data_resource.git mesh_data_resource```
|
||||
|
||||
5. Go up one folder
|
||||
|
||||
```cd ..```
|
||||
|
||||
6. Compile godot.
|
||||
|
||||
For example:
|
||||
|
||||
```scons p=x11 t=release_debug tools=yes```
|
@ -1,222 +0,0 @@
|
||||
# Terraman
|
||||
|
||||
A terrain engine for godot, focusing more on editor integration, gameplay-related features, and extendability (even from gdscript), without sacrificing too much speed.
|
||||
|
||||
It is a spinoff of [Voxelman](https://github.com/Relintai/voxelman). I started working on it when I realized that not only a full 3d voxel engine is too hard for me to use properly for an rpg (just think about how hard it is to do smooth zone - zone and dungeon transitions with the proper fidelity for an rpg), it's also unnecessary.
|
||||
|
||||
I could have technically implemented all of this into voxelman, as having only have one row of chunks, and then setting chunk height to 1, and creating a mesher that reads isolevel values as a normal height map will achieve the same effect. However as voxelman has lots of features with noises, lights and vertices, adding this on top of that module would have ended up being messy just for this reason alone (and also let's not forget the 3d apis).
|
||||
|
||||
So I ended up creating this. Everything works the same as in voxelman, but the apis have been simplified to make UX a bit better.
|
||||
|
||||
This is an engine module! Which means that you will need to compile it into Godot! [See the compiling section here.](#compiling)
|
||||
|
||||
You can grab pre-built binaries (even editor + export templates) from the [Broken Seals](https://github.com/Relintai/broken_seals/releases) repo.
|
||||
|
||||
## Godot Version Support
|
||||
|
||||
3.2 - Will likely work, probably needs changes by now. (TODO check.)\
|
||||
3.3 - Will more likely work, might need smaller changes by now. (TODO check.)\
|
||||
3.4 - Should work without any issues. (TODO check.)\
|
||||
3.x - Works.\
|
||||
4.0 - Have been fixing support from time to time. Currently it won't build. Mostly done with the fix though.
|
||||
|
||||
## Optional Dependencies
|
||||
|
||||
`https://github.com/Relintai/thread_pool`: Threaded chunk generation. Without this terraman is single threaded! \
|
||||
`https://github.com/Relintai/texture_packer`: You get access to [TerraLibraryMerger](#voxellibrarymerger) and [TerraLibraryMergerPCM](#voxellibrarymergerpcm). \
|
||||
`https://github.com/Relintai/mesh_data_resource`: You get access to a bunch of properties, and methods that can manipulate meshes.\
|
||||
`https://github.com/Relintai/props`: You get access to a bunch of properties, and methods that can manipulate, and use props.\
|
||||
`https://github.com/Relintai/mesh_utils`: Lets you use lod levels higher than 4 by default.
|
||||
|
||||
## Usage
|
||||
|
||||
First create a scene, and add a TerraWorldBlocky node into it. Create a TerraLibrary, and assign it to the Library property.
|
||||
Also, add a TerraSurface into your library.
|
||||
|
||||
Tick the editable property, deselect, then select the world again, and click the insert button at the top toolbar, or press B to insert a
|
||||
voxel at the inspector's camera's location.
|
||||
|
||||
Select the add button, and now you can just add voxels with the mouse, by clicking on the newly added voxel.
|
||||
|
||||
## TerraLibrary
|
||||
|
||||
This class stores the materials, and the TerraSurfaces.
|
||||
|
||||
Lod levels will automatically try to use materials of their own index.\
|
||||
For example lod level 1 will try to use material index 1, lod level 2 will try to use material index 2, etc.\
|
||||
If a material index is not available, they'll use the highest that is.\
|
||||
For example lod level 5 will try to get material index 5, but if you only have 3 materials it will use the 3rd.
|
||||
|
||||
### TerraLibrarySimple
|
||||
|
||||
The simplest library, just assign a material with a texture, and using the atlas_rows and atlas_culomns properties to tell the system
|
||||
how the UVs should be divided.
|
||||
|
||||
This is the basic Minecraft-style lib rary. Use this if you just have one texture atlas.
|
||||
|
||||
### TerraLibraryMerger
|
||||
|
||||
You will only have this if your godot also contains https://github.com/Relintai/texture_packer
|
||||
|
||||
You can assign any texture to your surfaces with this, and it will merge them together.
|
||||
|
||||
### TerraLibraryMergerPCM
|
||||
|
||||
(PCM = Per Chunk Material)
|
||||
|
||||
You will only have this if your godot also contains https://github.com/Relintai/texture_packer
|
||||
|
||||
You can assign any texture to your surfaces with this, and it will merge them together, but it will do it for every required chunk/voxel combination.
|
||||
|
||||
For example if you have a chunk with voxel Grass, and voxel Stone used in it, this library will create a material with a merged texture for Stone and Grass.
|
||||
If you have an anouther chunk which only has Grass and Stone in it, this material will be reused.
|
||||
And if you have a third chunk which only has a Grass voxel used in it, it will get a new merged material and texture only containing Grass voxel.
|
||||
|
||||
## Worlds
|
||||
|
||||
The 2 base classes. These won't do meshing on their own:
|
||||
|
||||
TerraWorld: Basic world, does not do anything until you implemnent the required virtual methods!\
|
||||
TerraWorldDefault: This adds threading, and LoD storage support to TerraWorld. Will not create meshes for you!
|
||||
|
||||
### TerraWorldBlocky
|
||||
|
||||
It generated UV mapped standard simple terrain meshes.
|
||||
The default algorithm can also generate normal lods.
|
||||
|
||||
### Level generation
|
||||
|
||||
Assign a TerraManLevelGenerator to the `World`'s `Level Generator` property.
|
||||
|
||||
You can write your own algorithm by implementing the ``` void _generate_chunk(chunk: TerraChunk) virtual ``` method.
|
||||
|
||||
`TerraManLevelGeneratorFlat` is also available, it will generate a floor for you, if you use it.
|
||||
|
||||
## TerraJobs
|
||||
|
||||
Producing just a terrain mesh for a chunk is not that hard by itself. However when you start adding layers/features
|
||||
like lod generation, collision meshes (especially since manipulating the physics server is not threadsafe),
|
||||
vertex lights, props, snapping props, props with vertex lights, etc
|
||||
chunk mesh generation can quickly become a serious mess.
|
||||
|
||||
TerraJobs are meant to solve the issue with less complexity.
|
||||
|
||||
They also provide a way to easily modularize mesh and lod generation.
|
||||
|
||||
### TerraJob
|
||||
|
||||
Base class for jobs.
|
||||
|
||||
If the [thread pool](https://github.com/Relintai/thread_pool) module is present, this is inherited from `ThreadPoolJob`,
|
||||
else it implements the same api as `ThreadPoolJob`, but it's not going to use threading.
|
||||
|
||||
A job has a reference to it's owner chunk.
|
||||
|
||||
If you implement your own jobs, when your job finishes call `next_job()`.
|
||||
|
||||
### TerraLightJob
|
||||
|
||||
This is the job that will generate vertex light based ao, random ao, and will bake your `TerraLight`s.
|
||||
|
||||
### TerraTerrain2DJob
|
||||
|
||||
This will generate your terrain collider and mesh (with lods) for you, using the meshers that you add into it.
|
||||
|
||||
Your lod setup is easily customizable with [TerraMesherJobSteps](https://github.com/Relintai/terraman/blob/master/world/jobs/voxel_mesher_job_step.h). The setup happens in your selected world's `_create_chunk` method.
|
||||
|
||||
### TerraProp2DJob
|
||||
|
||||
This will generate your prop meshes (with lods).
|
||||
|
||||
Also supports [TerraMesherJobSteps](https://github.com/Relintai/terraman/blob/master/world/jobs/voxel_mesher_job_step.h).
|
||||
|
||||
### Internal workings
|
||||
|
||||
#### TerraWorld
|
||||
|
||||
Whenever you want to spawn a chunk your World will create it using the ``` TerraChunk _create_chunk(x: int, y: int, z: int, chunk: TerraChunk) virtual ``` method.
|
||||
|
||||
Since properly initializing a chunk usually takes quite a few steps that you probably don't want to repeat everywhere the `chunk`
|
||||
parameter was added. This means you can just call the super `_create_chunk` methods, and you won't need to worry about your chunk
|
||||
getting overridden. Like:
|
||||
|
||||
Note that `_create_chunk` is also responsible for initializing chunks if you have them stored inside a scene.
|
||||
This is done by `setup_chunk(shunk)` in `TerraWorld`.
|
||||
|
||||
```
|
||||
func _create_chunk(x : int, y : int, z : int, chunk : TerraChunk) -> TerraChunk:
|
||||
if !chunk:
|
||||
chunk = MyChunk.new()
|
||||
|
||||
# We need to check whether or not we need to initialize jobs
|
||||
if chunk.job_get_count() == 0:
|
||||
# Setup a blocky (minecratf like) mesher job
|
||||
var tj : TerraTerrain2DJob = TerraTerrain2DJob.new()
|
||||
|
||||
var s : TerraMesherJobStep = TerraMesherJobStep.new()
|
||||
s.job_type = TerraMesherJobStep.TYPE_NORMAL
|
||||
tj.add_jobs_step(s)
|
||||
|
||||
tj.add_mesher(TerraMesherBlocky.new())
|
||||
tj.add_liquid_mesher(TerraMesherLiquidBlocky.new())
|
||||
|
||||
chunk.job_add(tj);
|
||||
|
||||
#setup your chunk here
|
||||
|
||||
return ._create_chunk(x, y, z, chunk)
|
||||
```
|
||||
|
||||
You can look at the world implementations for more examples: [TerraWorldBlocky](https://github.com/Relintai/terraman/blob/master/world/blocky/voxel_world_blocky.cpp).
|
||||
|
||||
#### TerraChunk
|
||||
|
||||
Stores terrain data, prop data. And mesh data (TerraChunkDefault), and the mesh generation jobs.
|
||||
|
||||
When it starts building meshes it will start submitting jobs to thread_pool one by one.
|
||||
|
||||
#### TerraMesher
|
||||
|
||||
If you want to implement your own meshing algorithm you can do so by overriding ``` void _add_chunk(chunk: TerraChunk) virtual ```.
|
||||
|
||||
TerraMesher works similarly to SurfaceTool, so first you need to set colors, uvs, etc and then call add_vertex.
|
||||
They won't get reset, so for example if you want all your vertices to have a certain color, you can get away with setting it only once.
|
||||
|
||||
## Compiling
|
||||
|
||||
First make sure that you can compile godot. See the official docs: https://docs.godotengine.org/en/3.x/development/compiling/index.html
|
||||
|
||||
1. Clone the engine if you haven't already:
|
||||
|
||||
If you want Godot 3.x:
|
||||
```git clone -b 3.x https://github.com/godotengine/godot.git godot```
|
||||
|
||||
If you want Godot 4.0:
|
||||
```git clone https://github.com/godotengine/godot.git godot```
|
||||
|
||||
|
||||
2. go into the modules folder inside the engine's directory:
|
||||
|
||||
```cd godot``` \
|
||||
```cd modules```
|
||||
|
||||
3. clone this repository
|
||||
|
||||
```git clone https://github.com/Relintai/terraman.git terraman```
|
||||
|
||||
(the folder needs to be named terraman!)
|
||||
|
||||
4. If you want the optional dependencies run these commands as well:
|
||||
|
||||
```git clone https://github.com/Relintai/texture_packer.git texture_packer``` \
|
||||
```git clone https://github.com/Relintai/mesh_data_resource.git mesh_data_resource```
|
||||
|
||||
5. Go up one folder
|
||||
|
||||
```cd ..```
|
||||
|
||||
6. Compile godot.
|
||||
|
||||
For example:
|
||||
|
||||
```scons p=x11 t=release_debug tools=yes```
|
@ -1,105 +0,0 @@
|
||||
# Texture Packer for the Godot Engine
|
||||
|
||||
This is a texture packer engine module, for the Godot Engine.
|
||||
|
||||
It can create texture atlases for you even in the running game.
|
||||
|
||||
It uses the legacy version of [rectpack2D](https://github.com/TeamHypersomnia/rectpack2D/tree/legacy)
|
||||
|
||||
It should work on all platforms.
|
||||
|
||||
It supports both godot 3.2 and 4.0 (master [last tested commit](https://github.com/godotengine/godot/commit/b7e10141197fdd9b0dbc4cfa7890329510d36540)). Note that since 4.0 is still in very early stages I only
|
||||
check whether it works from time to time.
|
||||
|
||||
# Pre-built binaries
|
||||
|
||||
You can grab a pre-built editor binary from the [Broken Seals](https://github.com/Relintai/broken_seals/releases)
|
||||
repo, should you want to. It contains all my modules.
|
||||
|
||||
# Building
|
||||
|
||||
1. Get the source code for the engine.
|
||||
|
||||
If you want Godot 3.2:
|
||||
```git clone -b 3.2 https://github.com/godotengine/godot.git godot```
|
||||
|
||||
If you want Godot 4.0:
|
||||
```git clone https://github.com/godotengine/godot.git godot```
|
||||
|
||||
|
||||
2. Go into Godot's modules directory.
|
||||
|
||||
```
|
||||
cd ./godot/modules/
|
||||
```
|
||||
|
||||
3. Clone this repository
|
||||
|
||||
```
|
||||
git clone https://github.com/Relintai/texture_packer texture_packer
|
||||
```
|
||||
|
||||
4. Build Godot. [Tutorial](https://docs.godotengine.org/en/latest/development/compiling/index.html)
|
||||
|
||||
# Features and Usage
|
||||
|
||||
## TexturePacker
|
||||
|
||||
This is the class that can merge textures. Add every texture you want into it using it's API (add_texture()), and then call merge().
|
||||
|
||||
add_texture() will return an AtlasTexture, this is the texture you want to use in your classes. It is immediately usable, it will just contain the original texture. Calling merge() will change it, to point to the new (merged) texture.
|
||||
|
||||
Supports filters, custom background color, margins.
|
||||
|
||||
### The keep_original_atlases option:
|
||||
|
||||
If you set this to true, and then add AtlasTextures, TexturePacker will change these ones (the ones you actually added)
|
||||
after the bake.
|
||||
|
||||
You can use this to bake gui textures together, without changing the resources everywhere at runtime.
|
||||
Think of rpgs, when you have a huge number of potential icons that the player can put on his or her actionbars.
|
||||
You can take look at Tales of Maj'Eyal or pretty much every actually complex MMORPGs as an example.
|
||||
|
||||
Note: Doing something like this in only recommended, if you can't pre-make the atlases (or it's really unfeasible), you are better off
|
||||
making the atlases yourself during development.
|
||||
|
||||
## TextureMerger
|
||||
|
||||
A Node that can bake textures for you. It uses TexturePacker internally.
|
||||
|
||||
It has an exposed Array, so you can assign textures to it in the editor.
|
||||
|
||||
## PackerImageResource
|
||||
|
||||
This is a simple Texture, which just contains an imported Image. It has no logic for drawing.
|
||||
|
||||
Useful for textures you only need for baking, as this class will not register it's data into the RenderingServer.
|
||||
|
||||
The module also contains an editor plugin which can import textures as `PackerImageResource` Resource.
|
||||
|
||||
To access it, click on a texture, switch to the import tab, and in the "Import As" Dropdown, select "Packer Image Recource".
|
||||
|
||||
## TextureLayerMerger
|
||||
|
||||
This class can merge together textures as layers. Useful for example to merge together (and color) skin, and clothes for a character.
|
||||
|
||||
It can handle both AtlasTextures and normal Textures.
|
||||
|
||||
Add the layers from bottom to top with the add_texture() method, when you added everything call merge().
|
||||
|
||||
You can set the resulting image's size with the `width`, and `height` properties. If you leave them at 0, they will
|
||||
change to the first added texture's size.
|
||||
|
||||
add_texture looks like this:
|
||||
|
||||
```
|
||||
void add_texture(Ref<Texture> p_texture, Color p_color = Color(1, 1, 1, 1), Vector2 p_position = Vector2(), Rect2 p_rect = Rect2());
|
||||
```
|
||||
|
||||
The color parameter will color the given texture on merge().
|
||||
With the position parameter you can offset your texture (in the resulted texture), and with the rect parameter, you can crop it.
|
||||
|
||||
There are setters to manipulate the added data later.
|
||||
|
||||
After the merge, you can either use `get_result_as_texture()` (it creates an ImageTexture on the fly), or the `data` property to
|
||||
grab the resulting Image.
|
@ -1,144 +0,0 @@
|
||||
# Thread pool module
|
||||
|
||||
A c++ Godot engine module, that will help you with threading.
|
||||
|
||||
It can also work if threads are not available (like on the javascript backend), in this case it runs jobs on the
|
||||
main thread. Jobs themselves can also distribute their work onto multiple frames, and you can set how much time
|
||||
is allocated per frame.
|
||||
|
||||
You can access it's setting from the `Project->Project Settings...` menu, in the `ThreadPool` category.
|
||||
|
||||
# Pre-built binaries
|
||||
|
||||
You can grab a pre-built editor binary from the [Broken Seals](https://github.com/Relintai/broken_seals/releases)
|
||||
repo, should you want to. It contains all my modules.
|
||||
|
||||
# ThreadPoolJob
|
||||
|
||||
Contains a job that can run on different threads.
|
||||
|
||||
A job is only considered finished, if you set the 'complete' property to 'true'. If multiple threads are available,
|
||||
the system will not check for this though, because there is no need.
|
||||
|
||||
If you want to support environments that doesn't have threading, you can use:
|
||||
|
||||
```
|
||||
bool should_do(const bool just_check = false);
|
||||
bool should_return();
|
||||
```
|
||||
|
||||
For example:
|
||||
|
||||
```
|
||||
func _execute():
|
||||
# On the first run this will return true, on subsequent runs it will return false
|
||||
if should_do():
|
||||
thing1()
|
||||
|
||||
# if you ran out the allocated timeframe in a frame, this will return true
|
||||
if should_return():
|
||||
return
|
||||
|
||||
if should_do():
|
||||
thing2()
|
||||
|
||||
if should_return():
|
||||
return
|
||||
|
||||
thing3()
|
||||
|
||||
complete = true
|
||||
|
||||
```
|
||||
|
||||
`should_do`'s optional parameter will let you just query the system, whether you finished a step, without
|
||||
incrementing internal couters. This is useful for example to distribute algorithms onto multiple frames.
|
||||
|
||||
For example:
|
||||
|
||||
```
|
||||
func _execute():
|
||||
if should_do(true):
|
||||
while current <= elements.size():
|
||||
#do heavy calculations
|
||||
|
||||
current += 1
|
||||
|
||||
if should_return():
|
||||
return
|
||||
|
||||
#The heavy calculation finished, increment counters
|
||||
should_do()
|
||||
|
||||
if should_return():
|
||||
return
|
||||
|
||||
if should_do():
|
||||
thing2()
|
||||
|
||||
if should_return():
|
||||
return
|
||||
|
||||
thing3()
|
||||
|
||||
complete = true
|
||||
|
||||
```
|
||||
|
||||
This class will need little tweaks, hopefully I can get to is soon.
|
||||
|
||||
# ThreadPoolExecuteJob
|
||||
|
||||
This will let you run a method uin an another thread, without creating your own jobs.
|
||||
|
||||
Use it through the ThreadPool Singleton. Like:
|
||||
|
||||
```
|
||||
ThreadPool.create_execute_job(self, "method", arg1, arg2, ...)
|
||||
|
||||
#or
|
||||
ThreadPool.create_execute_job_simple(self, "method")
|
||||
```
|
||||
|
||||
This class will need little tweaks, hopefully I can get to is soon.
|
||||
|
||||
# ThreadPool singleton
|
||||
|
||||
The ThreadPool singleton handles jobs.
|
||||
|
||||
If you have a job, submit it using `add_job`:
|
||||
|
||||
```
|
||||
MyJob job = MyJob.new()
|
||||
ThreadPool.add_job(job)
|
||||
```
|
||||
|
||||
It's api is still a bit messy, it will be cleaned up (hopefully very soon).
|
||||
|
||||
# Building
|
||||
|
||||
1. Get the source code for the engine.
|
||||
|
||||
If you want Godot 3.2:
|
||||
```git clone -b 3.2 https://github.com/godotengine/godot.git godot```
|
||||
|
||||
If you want Godot 4.0:
|
||||
```git clone https://github.com/godotengine/godot.git godot```
|
||||
|
||||
[last tested commit](https://github.com/godotengine/godot/commit/b7e10141197fdd9b0dbc4cfa7890329510d36540)
|
||||
|
||||
2. Go into Godot's modules directory.
|
||||
|
||||
```
|
||||
cd ./godot/modules/
|
||||
```
|
||||
|
||||
3. Clone this repository
|
||||
|
||||
```
|
||||
git clone https://github.com/Relintai/thread_pool thread_pool
|
||||
```
|
||||
|
||||
4. Build Godot. [Tutorial](https://docs.godotengine.org/en/latest/development/compiling/index.html)
|
||||
|
||||
|
@ -1,78 +0,0 @@
|
||||
# UI Extensions
|
||||
|
||||
This is a c++ engine module for the Godot engine, containing smaller utilities.
|
||||
|
||||
It supports both godot 3.2 and 4.0 (master [last tested commit](https://github.com/godotengine/godot/commit/b7e10141197fdd9b0dbc4cfa7890329510d36540)). Note that since 4.0 is still in very early stages I only
|
||||
check whether it works from time to time.
|
||||
|
||||
# Pre-built binaries
|
||||
|
||||
You can grab a pre-built editor binary from the [Broken Seals](https://github.com/Relintai/broken_seals/releases)
|
||||
repo, should you want to. It contains all my modules.
|
||||
|
||||
# TouchButton
|
||||
|
||||
A `Control` based button, that handles multitouch properly.
|
||||
|
||||
# BSInputEventKey
|
||||
|
||||
An `inputEventKey` implementation, that matches actions exactly.
|
||||
|
||||
For example with the default godot implementation if you have an action that gets triggered
|
||||
with the key `E` then `Ctrl-E` will also trigger it.
|
||||
|
||||
This has the side effect, that if you bind an action to `E`, and an another one to `Ctrl-E`,
|
||||
then hitting `Ctrl-E` will trigger both.
|
||||
|
||||
This implementation changes that behaviour.
|
||||
|
||||
However, you do need to replace normal input events at startup like this:
|
||||
|
||||
```
|
||||
func _ready():
|
||||
var actions : Array = InputMap.get_actions()
|
||||
|
||||
for action in actions:
|
||||
var acts : Array = InputMap.get_action_list(action)
|
||||
|
||||
for i in range(len(acts)):
|
||||
var a = acts[i]
|
||||
if a is InputEventKey:
|
||||
var nie : BSInputEventKey = BSInputEventKey.new()
|
||||
nie.from_input_event_key(a as InputEventKey)
|
||||
acts[i] = nie
|
||||
|
||||
InputMap.action_erase_event(action, a)
|
||||
InputMap.action_add_event(action, nie)
|
||||
|
||||
```
|
||||
|
||||
I recommend putting this code into a singleton.
|
||||
|
||||
# Building
|
||||
|
||||
1. Get the source code for the engine.
|
||||
|
||||
If you want Godot 3.2:
|
||||
```git clone -b 3.2 https://github.com/godotengine/godot.git godot```
|
||||
|
||||
If you want Godot 4.0:
|
||||
```git clone https://github.com/godotengine/godot.git godot```
|
||||
|
||||
|
||||
2. Go into Godot's modules directory.
|
||||
|
||||
```
|
||||
cd ./godot/modules/
|
||||
```
|
||||
|
||||
3. Clone this repository
|
||||
|
||||
```
|
||||
git clone https://github.com/Relintai/ui_extensions ui_extensions
|
||||
```
|
||||
|
||||
4. Build Godot. [Tutorial](https://docs.godotengine.org/en/latest/development/compiling/index.html)
|
||||
|
||||
|
||||
|
@ -1,227 +0,0 @@
|
||||
# Voxelman
|
||||
|
||||
A voxel engine for godot, focusing more on editor integration, gameplay-related features, and extendability (even from gdscript), without sacrificing too much speed.
|
||||
|
||||
This is an engine module! Which means that you will need to compile it into Godot! [See the compiling section here.](#compiling)
|
||||
|
||||
You can find a demonstration project (with pre-built binaries) here: https://github.com/Relintai/the_tower
|
||||
|
||||
## Godot Version Support
|
||||
|
||||
I'm currently mostly using [Terraman](https://github.com/Relintai/terraman) instead of this, so it might get temporarily a bit behind.\
|
||||
If compile breaks, and I don't notice please report.
|
||||
|
||||
3.2 - Will likely work, probably needs changes by now. (TODO check.)\
|
||||
3.3 - Will more likely work, might need smaller changes by now. (TODO check.)\
|
||||
3.4 - Should work without any issues. (TODO check.)\
|
||||
3.x - Works. [last tested commit](6ea58db2d849d9ca0ccee5bc6a6d2b919d404bc1)\
|
||||
4.0 - Have been fixing support from time to time. Currently it won't build. Mostly done with the fix though.
|
||||
|
||||
## Optional Dependencies
|
||||
|
||||
`https://github.com/Relintai/texture_packer`: You get access to [VoxelLibraryMerger](#voxellibrarymerger) and [VoxelLibraryMergerPCM](#voxellibrarymergerpcm). \
|
||||
`https://github.com/Relintai/mesh_data_resource`: You get access to a bunch of properties, and methods that can manipulate meshes.\
|
||||
`https://github.com/Relintai/props`: You get access to a bunch of properties, and methods that can manipulate, and use props.\
|
||||
`https://github.com/Relintai/mesh_utils`: Lets you use lod levels higher than 4 by default.
|
||||
|
||||
## Usage
|
||||
|
||||
First create a scene, and add a VoxelWorldBlocky / VoxelWorldMarchingCubes node into it. Create a VoxelLibrary, and assign it to the Library property.
|
||||
Also, add a VoxelSurface into your library.
|
||||
|
||||
Tick the editable property, deselect, then select the world again, and click the insert button at the top toolbar, or press B to insert a
|
||||
voxel at the inspector's camera's location.
|
||||
|
||||
Select the add button, and now you can just add voxels with the mouse, by clicking on the newly added voxel.
|
||||
|
||||
## VoxelLibrary
|
||||
|
||||
This class stores the materials, and the VoxelSurfaces.
|
||||
|
||||
Lod levels will automatically try to use materials of their own index.\
|
||||
For example lod level 1 will try to use material index 1, lod level 2 will try to use material index 2, etc.\
|
||||
If a material index is not available, they'll use the highest that is.\
|
||||
For example lod level 5 will try to get material index 5, but if you only have 3 materials it will use the 3rd.
|
||||
|
||||
### VoxelLibrarySimple
|
||||
|
||||
The simplest library, just assign a material with a texture, and using the atlas_rows and atlas_culomns properties to tell the system
|
||||
how the UVs should be divided.
|
||||
|
||||
This is the basic Minecraft-style lib rary. Use this if you just have one texture atlas.
|
||||
|
||||
### VoxelLibraryMerger
|
||||
|
||||
You will only have this if your godot also contains https://github.com/Relintai/texture_packer
|
||||
|
||||
You can assign any texture to your surfaces with this, and it will merge them together.
|
||||
|
||||
### VoxelLibraryMergerPCM
|
||||
|
||||
(PCM = Per Chunk Material)
|
||||
|
||||
You will only have this if your godot also contains https://github.com/Relintai/texture_packer
|
||||
|
||||
You can assign any texture to your surfaces with this, and it will merge them together, but it will do it for every required chunk/voxel combination.
|
||||
|
||||
For example if you have a chunk with voxel Grass, and voxel Stone used in it, this library will create a material with a merged texture for Stone and Grass.
|
||||
If you have an anouther chunk which only has Grass and Stone in it, this material will be reused.
|
||||
And if you have a third chunk which only has a Grass voxel used in it, it will get a new merged material and texture only containing Grass voxel.
|
||||
|
||||
## Worlds
|
||||
|
||||
The 2 base classes. These won't do meshing on their own:
|
||||
|
||||
VoxelWorld: Basic world, does not do anything until you implemnent the required virtual methods!\
|
||||
VoxelWorldDefault: This adds threading, and LoD storage support to VoxelWorld. Will not create meshes for you!
|
||||
|
||||
### VoxelWorldBlocky
|
||||
|
||||
The most basic world. It is the Minecraft-style world.
|
||||
|
||||
### VoxelWorldMarchingCubes
|
||||
|
||||
A marching cubes based Voxel World. Actually it uses a modified version of the Transvoxel tables, because it is UV mapped.
|
||||
|
||||
### VoxelWorldCubic
|
||||
|
||||
This is my own meshing algorithm, it's basically a Minecraft style mesher that can take isolevel into account.
|
||||
|
||||
It's kind of a pain to use, it might get removed.
|
||||
|
||||
### Level generation
|
||||
|
||||
Assign a VoxelManLevelGenerator to the `World`'s `Level Generator` property.
|
||||
|
||||
You can write your own algorithm by implementing the ``` void _generate_chunk(chunk: VoxelChunk) virtual ``` method.
|
||||
|
||||
`VoxelManLevelGeneratorFlat` is also available, it will generate a floor for you, if you use it.
|
||||
|
||||
## VoxelJobs
|
||||
|
||||
Producing just a terrain mesh for a chunk is not that hard by itself. However when you start adding layers/features
|
||||
like lod generation, collision meshes (especially since manipulating the physics server is not threadsafe),
|
||||
vertex lights, props, snapping props, props with vertex lights, etc
|
||||
chunk mesh generation can quickly become a serious mess.
|
||||
|
||||
VoxelJobs are meant to solve the issue with less complexity.
|
||||
|
||||
They also provide a way to easily modularize mesh and lod generation.
|
||||
|
||||
### VoxelJob
|
||||
|
||||
Base class for jobs.
|
||||
|
||||
If the [thread pool](https://github.com/Relintai/thread_pool) module is present, this is inherited from `ThreadPoolJob`,
|
||||
else it implements the same api as `ThreadPoolJob`, but it's not going to use threading.
|
||||
|
||||
A job has a reference to it's owner chunk.
|
||||
|
||||
If you implement your own jobs, when your job finishes call `next_job()`.
|
||||
|
||||
### VoxelLightJob
|
||||
|
||||
This is the job that will generate vertex light based ao, random ao, and will bake your `VoxelLight`s.
|
||||
|
||||
### VoxelTerrainJob
|
||||
|
||||
This will generate your terrain collider and mesh (with lods) for you, using the meshers that you add into it.
|
||||
|
||||
Your lod setup is easily customizable with [VoxelMesherJobSteps](https://github.com/Relintai/voxelman/blob/master/world/jobs/voxel_mesher_job_step.h). The setup happens in your selected world's `_create_chunk` method.
|
||||
|
||||
### VoxelPropJob
|
||||
|
||||
This will generate your prop meshes (with lods).
|
||||
|
||||
Also supports [VoxelMesherJobSteps](https://github.com/Relintai/voxelman/blob/master/world/jobs/voxel_mesher_job_step.h).
|
||||
|
||||
### Internal workings
|
||||
|
||||
#### VoxelWorld
|
||||
|
||||
Whenever you want to spawn a chunk your World will create it using the ``` VoxelChunk _create_chunk(x: int, y: int, z: int, chunk: VoxelChunk) virtual ``` method.
|
||||
|
||||
Since properly initializing a chunk usually takes quite a few steps that you probably don't want to repeat everywhere the `chunk`
|
||||
parameter was added. This means you can just call the super `_create_chunk` methods, and you won't need to worry about your chunk
|
||||
getting overridden. Like:
|
||||
|
||||
Note that `_create_chunk` is also responsible for initializing chunks if you have them stored inside a scene.
|
||||
This is done by `setup_chunk(shunk)` in `VoxelWorld`.
|
||||
|
||||
```
|
||||
func _create_chunk(x : int, y : int, z : int, chunk : VoxelChunk) -> VoxelChunk:
|
||||
if !chunk:
|
||||
chunk = MyChunk.new()
|
||||
|
||||
# We need to check whether or not we need to initialize jobs
|
||||
if chunk.job_get_count() == 0:
|
||||
# Setup a blocky (minecratf like) mesher job
|
||||
var tj : VoxelTerrainJob = VoxelTerrainJob.new()
|
||||
|
||||
var s : VoxelMesherJobStep = VoxelMesherJobStep.new()
|
||||
s.job_type = VoxelMesherJobStep.TYPE_NORMAL
|
||||
tj.add_jobs_step(s)
|
||||
|
||||
tj.add_mesher(VoxelMesherBlocky.new())
|
||||
tj.add_liquid_mesher(VoxelMesherLiquidBlocky.new())
|
||||
|
||||
chunk.job_add(tj);
|
||||
|
||||
#setup your chunk here
|
||||
|
||||
return ._create_chunk(x, y, z, chunk)
|
||||
```
|
||||
|
||||
You can look at the world implementations for more examples: [VoxelWorldBlocky](https://github.com/Relintai/voxelman/blob/master/world/blocky/voxel_world_blocky.cpp), [VoxelWorldMarchingCubes](https://github.com/Relintai/voxelman/blob/master/world/marching_cubes/voxel_world_marching_cubes.cpp).
|
||||
|
||||
#### VoxelChunk
|
||||
|
||||
Stores terrain data, prop data. And mesh data (VoxelChunkDefault), and the mesh generation jobs.
|
||||
|
||||
When it starts building meshes it will start submitting jobs to thread_pool one by one.
|
||||
|
||||
#### VoxelMesher
|
||||
|
||||
If you want to implement your own meshing algorithm you can do so by overriding ``` void _add_chunk(chunk: VoxelChunk) virtual ```.
|
||||
|
||||
VoxelMesher works similarly to SurfaceTool, so first you need to set colors, uvs, etc and then call add_vertex.
|
||||
They won't get reset, so for example if you want all your vertices to have a certain color, you can get away with setting it only once.
|
||||
|
||||
## Compiling
|
||||
|
||||
First make sure that you can compile godot. See the official docs: https://docs.godotengine.org/en/3.x/development/compiling/index.html
|
||||
|
||||
1. Clone the engine if you haven't already:
|
||||
|
||||
If you want Godot 3.x:
|
||||
```git clone -b 3.x https://github.com/godotengine/godot.git godot```
|
||||
|
||||
If you want Godot 4.0:
|
||||
```git clone https://github.com/godotengine/godot.git godot```
|
||||
|
||||
|
||||
2. go into the modules folder inside the engine's directory:
|
||||
|
||||
```cd godot``` \
|
||||
```cd modules```
|
||||
|
||||
3. clone this repository
|
||||
|
||||
```git clone https://github.com/Relintai/voxelman.git voxelman```
|
||||
|
||||
(the folder needs to be named voxelman!)
|
||||
|
||||
4. If you want the optional dependencies run these commands as well:
|
||||
|
||||
```git clone https://github.com/Relintai/texture_packer.git texture_packer``` \
|
||||
```git clone https://github.com/Relintai/mesh_data_resource.git mesh_data_resource```
|
||||
|
||||
5. Go up one folder
|
||||
|
||||
```cd ..```
|
||||
|
||||
6. Compile godot.
|
||||
|
||||
For example:
|
||||
|
||||
```scons p=x11 t=release_debug tools=yes```
|
@ -1,237 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import sys
|
||||
import xml.etree.ElementTree as ET
|
||||
|
||||
|
||||
tree = ET.parse(sys.argv[1])
|
||||
old_doc = tree.getroot()
|
||||
|
||||
tree = ET.parse(sys.argv[2])
|
||||
new_doc = tree.getroot()
|
||||
|
||||
f = file(sys.argv[3], "wb")
|
||||
tab = 0
|
||||
|
||||
old_classes = {}
|
||||
|
||||
|
||||
def write_string(_f, text, newline=True):
|
||||
for t in range(tab):
|
||||
_f.write("\t")
|
||||
_f.write(text)
|
||||
if newline:
|
||||
_f.write("\n")
|
||||
|
||||
|
||||
def escape(ret):
|
||||
ret = ret.replace("&", "&")
|
||||
ret = ret.replace("<", ">")
|
||||
ret = ret.replace(">", "<")
|
||||
ret = ret.replace("'", "'")
|
||||
ret = ret.replace('"', """)
|
||||
return ret
|
||||
|
||||
|
||||
def inc_tab():
|
||||
global tab
|
||||
tab += 1
|
||||
|
||||
|
||||
def dec_tab():
|
||||
global tab
|
||||
tab -= 1
|
||||
|
||||
|
||||
write_string(f, '<?xml version="1.0" encoding="UTF-8" ?>')
|
||||
write_string(f, '<doc version="' + new_doc.attrib["version"] + '">')
|
||||
|
||||
|
||||
def get_tag(node, name):
|
||||
tag = ""
|
||||
if name in node.attrib:
|
||||
tag = " " + name + '="' + escape(node.attrib[name]) + '" '
|
||||
return tag
|
||||
|
||||
|
||||
def find_method_descr(old_class, name):
|
||||
|
||||
methods = old_class.find("methods")
|
||||
if methods != None and len(list(methods)) > 0:
|
||||
for m in list(methods):
|
||||
if m.attrib["name"] == name:
|
||||
description = m.find("description")
|
||||
if description != None and description.text.strip() != "":
|
||||
return description.text
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def find_signal_descr(old_class, name):
|
||||
|
||||
signals = old_class.find("signals")
|
||||
if signals != None and len(list(signals)) > 0:
|
||||
for m in list(signals):
|
||||
if m.attrib["name"] == name:
|
||||
description = m.find("description")
|
||||
if description != None and description.text.strip() != "":
|
||||
return description.text
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def find_constant_descr(old_class, name):
|
||||
|
||||
if old_class is None:
|
||||
return None
|
||||
constants = old_class.find("constants")
|
||||
if constants != None and len(list(constants)) > 0:
|
||||
for m in list(constants):
|
||||
if m.attrib["name"] == name:
|
||||
if m.text.strip() != "":
|
||||
return m.text
|
||||
return None
|
||||
|
||||
|
||||
def write_class(c):
|
||||
class_name = c.attrib["name"]
|
||||
print("Parsing Class: " + class_name)
|
||||
if class_name in old_classes:
|
||||
old_class = old_classes[class_name]
|
||||
else:
|
||||
old_class = None
|
||||
|
||||
category = get_tag(c, "category")
|
||||
inherits = get_tag(c, "inherits")
|
||||
write_string(f, '<class name="' + class_name + '" ' + category + inherits + ">")
|
||||
inc_tab()
|
||||
|
||||
write_string(f, "<brief_description>")
|
||||
|
||||
if old_class != None:
|
||||
old_brief_descr = old_class.find("brief_description")
|
||||
if old_brief_descr != None:
|
||||
write_string(f, escape(old_brief_descr.text.strip()))
|
||||
|
||||
write_string(f, "</brief_description>")
|
||||
|
||||
write_string(f, "<description>")
|
||||
if old_class != None:
|
||||
old_descr = old_class.find("description")
|
||||
if old_descr != None:
|
||||
write_string(f, escape(old_descr.text.strip()))
|
||||
|
||||
write_string(f, "</description>")
|
||||
|
||||
methods = c.find("methods")
|
||||
if methods != None and len(list(methods)) > 0:
|
||||
|
||||
write_string(f, "<methods>")
|
||||
inc_tab()
|
||||
|
||||
for m in list(methods):
|
||||
qualifiers = get_tag(m, "qualifiers")
|
||||
|
||||
write_string(f, '<method name="' + escape(m.attrib["name"]) + '" ' + qualifiers + ">")
|
||||
inc_tab()
|
||||
|
||||
for a in list(m):
|
||||
if a.tag == "return":
|
||||
typ = get_tag(a, "type")
|
||||
write_string(f, "<return" + typ + ">")
|
||||
write_string(f, "</return>")
|
||||
elif a.tag == "argument":
|
||||
|
||||
default = get_tag(a, "default")
|
||||
|
||||
write_string(
|
||||
f,
|
||||
'<argument index="'
|
||||
+ a.attrib["index"]
|
||||
+ '" name="'
|
||||
+ escape(a.attrib["name"])
|
||||
+ '" type="'
|
||||
+ a.attrib["type"]
|
||||
+ '"'
|
||||
+ default
|
||||
+ ">",
|
||||
)
|
||||
write_string(f, "</argument>")
|
||||
|
||||
write_string(f, "<description>")
|
||||
if old_class != None:
|
||||
old_method_descr = find_method_descr(old_class, m.attrib["name"])
|
||||
if old_method_descr:
|
||||
write_string(f, escape(escape(old_method_descr.strip())))
|
||||
|
||||
write_string(f, "</description>")
|
||||
dec_tab()
|
||||
write_string(f, "</method>")
|
||||
dec_tab()
|
||||
write_string(f, "</methods>")
|
||||
|
||||
signals = c.find("signals")
|
||||
if signals != None and len(list(signals)) > 0:
|
||||
|
||||
write_string(f, "<signals>")
|
||||
inc_tab()
|
||||
|
||||
for m in list(signals):
|
||||
|
||||
write_string(f, '<signal name="' + escape(m.attrib["name"]) + '">')
|
||||
inc_tab()
|
||||
|
||||
for a in list(m):
|
||||
if a.tag == "argument":
|
||||
|
||||
write_string(
|
||||
f,
|
||||
'<argument index="'
|
||||
+ a.attrib["index"]
|
||||
+ '" name="'
|
||||
+ escape(a.attrib["name"])
|
||||
+ '" type="'
|
||||
+ a.attrib["type"]
|
||||
+ '">',
|
||||
)
|
||||
write_string(f, "</argument>")
|
||||
|
||||
write_string(f, "<description>")
|
||||
if old_class != None:
|
||||
old_signal_descr = find_signal_descr(old_class, m.attrib["name"])
|
||||
if old_signal_descr:
|
||||
write_string(f, escape(old_signal_descr.strip()))
|
||||
write_string(f, "</description>")
|
||||
dec_tab()
|
||||
write_string(f, "</signal>")
|
||||
dec_tab()
|
||||
write_string(f, "</signals>")
|
||||
|
||||
constants = c.find("constants")
|
||||
if constants != None and len(list(constants)) > 0:
|
||||
|
||||
write_string(f, "<constants>")
|
||||
inc_tab()
|
||||
|
||||
for m in list(constants):
|
||||
|
||||
write_string(f, '<constant name="' + escape(m.attrib["name"]) + '" value="' + m.attrib["value"] + '">')
|
||||
old_constant_descr = find_constant_descr(old_class, m.attrib["name"])
|
||||
if old_constant_descr:
|
||||
write_string(f, escape(old_constant_descr.strip()))
|
||||
write_string(f, "</constant>")
|
||||
|
||||
dec_tab()
|
||||
write_string(f, "</constants>")
|
||||
|
||||
dec_tab()
|
||||
write_string(f, "</class>")
|
||||
|
||||
|
||||
for c in list(old_doc):
|
||||
old_classes[c.attrib["name"]] = c
|
||||
|
||||
for c in list(new_doc):
|
||||
write_class(c)
|
||||
write_string(f, "</doc>\n")
|
@ -1,495 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
import fnmatch
|
||||
import os
|
||||
import sys
|
||||
import re
|
||||
import math
|
||||
import platform
|
||||
import xml.etree.ElementTree as ET
|
||||
|
||||
################################################################################
|
||||
# Config #
|
||||
################################################################################
|
||||
|
||||
flags = {
|
||||
"c": platform.platform() != "Windows", # Disable by default on windows, since we use ANSI escape codes
|
||||
"b": False,
|
||||
"g": False,
|
||||
"s": False,
|
||||
"u": False,
|
||||
"h": False,
|
||||
"p": False,
|
||||
"o": True,
|
||||
"i": False,
|
||||
"a": True,
|
||||
"e": False,
|
||||
}
|
||||
flag_descriptions = {
|
||||
"c": "Toggle colors when outputting.",
|
||||
"b": "Toggle showing only not fully described classes.",
|
||||
"g": "Toggle showing only completed classes.",
|
||||
"s": "Toggle showing comments about the status.",
|
||||
"u": "Toggle URLs to docs.",
|
||||
"h": "Show help and exit.",
|
||||
"p": "Toggle showing percentage as well as counts.",
|
||||
"o": "Toggle overall column.",
|
||||
"i": "Toggle collapse of class items columns.",
|
||||
"a": "Toggle showing all items.",
|
||||
"e": "Toggle hiding empty items.",
|
||||
}
|
||||
long_flags = {
|
||||
"colors": "c",
|
||||
"use-colors": "c",
|
||||
"bad": "b",
|
||||
"only-bad": "b",
|
||||
"good": "g",
|
||||
"only-good": "g",
|
||||
"comments": "s",
|
||||
"status": "s",
|
||||
"urls": "u",
|
||||
"gen-url": "u",
|
||||
"help": "h",
|
||||
"percent": "p",
|
||||
"use-percentages": "p",
|
||||
"overall": "o",
|
||||
"use-overall": "o",
|
||||
"items": "i",
|
||||
"collapse": "i",
|
||||
"all": "a",
|
||||
"empty": "e",
|
||||
}
|
||||
table_columns = [
|
||||
"name",
|
||||
"brief_description",
|
||||
"description",
|
||||
"methods",
|
||||
"constants",
|
||||
"members",
|
||||
"signals",
|
||||
"theme_items",
|
||||
]
|
||||
table_column_names = ["Name", "Brief Desc.", "Desc.", "Methods", "Constants", "Members", "Signals", "Theme Items"]
|
||||
colors = {
|
||||
"name": [36], # cyan
|
||||
"part_big_problem": [4, 31], # underline, red
|
||||
"part_problem": [31], # red
|
||||
"part_mostly_good": [33], # yellow
|
||||
"part_good": [32], # green
|
||||
"url": [4, 34], # underline, blue
|
||||
"section": [1, 4], # bold, underline
|
||||
"state_off": [36], # cyan
|
||||
"state_on": [1, 35], # bold, magenta/plum
|
||||
"bold": [1], # bold
|
||||
}
|
||||
overall_progress_description_weigth = 10
|
||||
|
||||
|
||||
################################################################################
|
||||
# Utils #
|
||||
################################################################################
|
||||
|
||||
|
||||
def validate_tag(elem, tag):
|
||||
if elem.tag != tag:
|
||||
print('Tag mismatch, expected "' + tag + '", got ' + elem.tag)
|
||||
sys.exit(255)
|
||||
|
||||
|
||||
def color(color, string):
|
||||
if flags["c"] and terminal_supports_color():
|
||||
color_format = ""
|
||||
for code in colors[color]:
|
||||
color_format += "\033[" + str(code) + "m"
|
||||
return color_format + string + "\033[0m"
|
||||
else:
|
||||
return string
|
||||
|
||||
|
||||
ansi_escape = re.compile(r"\x1b[^m]*m")
|
||||
|
||||
|
||||
def nonescape_len(s):
|
||||
return len(ansi_escape.sub("", s))
|
||||
|
||||
|
||||
def terminal_supports_color():
|
||||
p = sys.platform
|
||||
supported_platform = p != "Pocket PC" and (p != "win32" or "ANSICON" in os.environ)
|
||||
|
||||
is_a_tty = hasattr(sys.stdout, "isatty") and sys.stdout.isatty()
|
||||
if not supported_platform or not is_a_tty:
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
################################################################################
|
||||
# Classes #
|
||||
################################################################################
|
||||
|
||||
|
||||
class ClassStatusProgress:
|
||||
def __init__(self, described=0, total=0):
|
||||
self.described = described
|
||||
self.total = total
|
||||
|
||||
def __add__(self, other):
|
||||
return ClassStatusProgress(self.described + other.described, self.total + other.total)
|
||||
|
||||
def increment(self, described):
|
||||
if described:
|
||||
self.described += 1
|
||||
self.total += 1
|
||||
|
||||
def is_ok(self):
|
||||
return self.described >= self.total
|
||||
|
||||
def to_configured_colored_string(self):
|
||||
if flags["p"]:
|
||||
return self.to_colored_string("{percent}% ({has}/{total})", "{pad_percent}{pad_described}{s}{pad_total}")
|
||||
else:
|
||||
return self.to_colored_string()
|
||||
|
||||
def to_colored_string(self, format="{has}/{total}", pad_format="{pad_described}{s}{pad_total}"):
|
||||
ratio = float(self.described) / float(self.total) if self.total != 0 else 1
|
||||
percent = int(round(100 * ratio))
|
||||
s = format.format(has=str(self.described), total=str(self.total), percent=str(percent))
|
||||
if self.described >= self.total:
|
||||
s = color("part_good", s)
|
||||
elif self.described >= self.total / 4 * 3:
|
||||
s = color("part_mostly_good", s)
|
||||
elif self.described > 0:
|
||||
s = color("part_problem", s)
|
||||
else:
|
||||
s = color("part_big_problem", s)
|
||||
pad_size = max(len(str(self.described)), len(str(self.total)))
|
||||
pad_described = "".ljust(pad_size - len(str(self.described)))
|
||||
pad_percent = "".ljust(3 - len(str(percent)))
|
||||
pad_total = "".ljust(pad_size - len(str(self.total)))
|
||||
return pad_format.format(pad_described=pad_described, pad_total=pad_total, pad_percent=pad_percent, s=s)
|
||||
|
||||
|
||||
class ClassStatus:
|
||||
def __init__(self, name=""):
|
||||
self.name = name
|
||||
self.has_brief_description = True
|
||||
self.has_description = True
|
||||
self.progresses = {
|
||||
"methods": ClassStatusProgress(),
|
||||
"constants": ClassStatusProgress(),
|
||||
"members": ClassStatusProgress(),
|
||||
"theme_items": ClassStatusProgress(),
|
||||
"signals": ClassStatusProgress(),
|
||||
}
|
||||
|
||||
def __add__(self, other):
|
||||
new_status = ClassStatus()
|
||||
new_status.name = self.name
|
||||
new_status.has_brief_description = self.has_brief_description and other.has_brief_description
|
||||
new_status.has_description = self.has_description and other.has_description
|
||||
for k in self.progresses:
|
||||
new_status.progresses[k] = self.progresses[k] + other.progresses[k]
|
||||
return new_status
|
||||
|
||||
def is_ok(self):
|
||||
ok = True
|
||||
ok = ok and self.has_brief_description
|
||||
ok = ok and self.has_description
|
||||
for k in self.progresses:
|
||||
ok = ok and self.progresses[k].is_ok()
|
||||
return ok
|
||||
|
||||
def is_empty(self):
|
||||
sum = 0
|
||||
for k in self.progresses:
|
||||
if self.progresses[k].is_ok():
|
||||
continue
|
||||
sum += self.progresses[k].total
|
||||
return sum < 1
|
||||
|
||||
def make_output(self):
|
||||
output = {}
|
||||
output["name"] = color("name", self.name)
|
||||
|
||||
ok_string = color("part_good", "OK")
|
||||
missing_string = color("part_big_problem", "MISSING")
|
||||
|
||||
output["brief_description"] = ok_string if self.has_brief_description else missing_string
|
||||
output["description"] = ok_string if self.has_description else missing_string
|
||||
|
||||
description_progress = ClassStatusProgress(
|
||||
(self.has_brief_description + self.has_description) * overall_progress_description_weigth,
|
||||
2 * overall_progress_description_weigth,
|
||||
)
|
||||
items_progress = ClassStatusProgress()
|
||||
|
||||
for k in ["methods", "constants", "members", "signals", "theme_items"]:
|
||||
items_progress += self.progresses[k]
|
||||
output[k] = self.progresses[k].to_configured_colored_string()
|
||||
|
||||
output["items"] = items_progress.to_configured_colored_string()
|
||||
|
||||
output["overall"] = (description_progress + items_progress).to_colored_string(
|
||||
color("bold", "{percent}%"), "{pad_percent}{s}"
|
||||
)
|
||||
|
||||
if self.name.startswith("Total"):
|
||||
output["url"] = color("url", "https://docs.godotengine.org/en/latest/classes/")
|
||||
if flags["s"]:
|
||||
output["comment"] = color("part_good", "ALL OK")
|
||||
else:
|
||||
output["url"] = color(
|
||||
"url", "https://docs.godotengine.org/en/latest/classes/class_{name}.html".format(name=self.name.lower())
|
||||
)
|
||||
|
||||
if flags["s"] and not flags["g"] and self.is_ok():
|
||||
output["comment"] = color("part_good", "ALL OK")
|
||||
|
||||
return output
|
||||
|
||||
@staticmethod
|
||||
def generate_for_class(c):
|
||||
status = ClassStatus()
|
||||
status.name = c.attrib["name"]
|
||||
|
||||
for tag in list(c):
|
||||
|
||||
if tag.tag == "brief_description":
|
||||
status.has_brief_description = len(tag.text.strip()) > 0
|
||||
|
||||
elif tag.tag == "description":
|
||||
status.has_description = len(tag.text.strip()) > 0
|
||||
|
||||
elif tag.tag in ["methods", "signals"]:
|
||||
for sub_tag in list(tag):
|
||||
descr = sub_tag.find("description")
|
||||
status.progresses[tag.tag].increment(len(descr.text.strip()) > 0)
|
||||
elif tag.tag in ["constants", "members", "theme_items"]:
|
||||
for sub_tag in list(tag):
|
||||
if not sub_tag.text is None:
|
||||
status.progresses[tag.tag].increment(len(sub_tag.text.strip()) > 0)
|
||||
|
||||
elif tag.tag in ["tutorials"]:
|
||||
pass # Ignore those tags for now
|
||||
|
||||
elif tag.tag in ["theme_items"]:
|
||||
pass # Ignore those tags, since they seem to lack description at all
|
||||
|
||||
else:
|
||||
print(tag.tag, tag.attrib)
|
||||
|
||||
return status
|
||||
|
||||
|
||||
################################################################################
|
||||
# Arguments #
|
||||
################################################################################
|
||||
|
||||
input_file_list = []
|
||||
input_class_list = []
|
||||
merged_file = ""
|
||||
|
||||
for arg in sys.argv[1:]:
|
||||
try:
|
||||
if arg.startswith("--"):
|
||||
flags[long_flags[arg[2:]]] = not flags[long_flags[arg[2:]]]
|
||||
elif arg.startswith("-"):
|
||||
for f in arg[1:]:
|
||||
flags[f] = not flags[f]
|
||||
elif os.path.isdir(arg):
|
||||
for f in os.listdir(arg):
|
||||
if f.endswith(".xml"):
|
||||
input_file_list.append(os.path.join(arg, f))
|
||||
else:
|
||||
input_class_list.append(arg)
|
||||
except KeyError:
|
||||
print("Unknown command line flag: " + arg)
|
||||
sys.exit(1)
|
||||
|
||||
if flags["i"]:
|
||||
for r in ["methods", "constants", "members", "signals", "theme_items"]:
|
||||
index = table_columns.index(r)
|
||||
del table_column_names[index]
|
||||
del table_columns[index]
|
||||
table_column_names.append("Items")
|
||||
table_columns.append("items")
|
||||
|
||||
if flags["o"] == (not flags["i"]):
|
||||
table_column_names.append(color("bold", "Overall"))
|
||||
table_columns.append("overall")
|
||||
|
||||
if flags["u"]:
|
||||
table_column_names.append("Docs URL")
|
||||
table_columns.append("url")
|
||||
|
||||
|
||||
################################################################################
|
||||
# Help #
|
||||
################################################################################
|
||||
|
||||
if len(input_file_list) < 1 or flags["h"]:
|
||||
if not flags["h"]:
|
||||
print(color("section", "Invalid usage") + ": Please specify a classes directory")
|
||||
print(color("section", "Usage") + ": doc_status.py [flags] <classes_dir> [class names]")
|
||||
print("\t< and > signify required parameters, while [ and ] signify optional parameters.")
|
||||
print(color("section", "Available flags") + ":")
|
||||
possible_synonym_list = list(long_flags)
|
||||
possible_synonym_list.sort()
|
||||
flag_list = list(flags)
|
||||
flag_list.sort()
|
||||
for flag in flag_list:
|
||||
synonyms = [color("name", "-" + flag)]
|
||||
for synonym in possible_synonym_list:
|
||||
if long_flags[synonym] == flag:
|
||||
synonyms.append(color("name", "--" + synonym))
|
||||
|
||||
print(
|
||||
(
|
||||
"{synonyms} (Currently "
|
||||
+ color("state_" + ("on" if flags[flag] else "off"), "{value}")
|
||||
+ ")\n\t{description}"
|
||||
).format(
|
||||
synonyms=", ".join(synonyms),
|
||||
value=("on" if flags[flag] else "off"),
|
||||
description=flag_descriptions[flag],
|
||||
)
|
||||
)
|
||||
sys.exit(0)
|
||||
|
||||
|
||||
################################################################################
|
||||
# Parse class list #
|
||||
################################################################################
|
||||
|
||||
class_names = []
|
||||
classes = {}
|
||||
|
||||
for file in input_file_list:
|
||||
tree = ET.parse(file)
|
||||
doc = tree.getroot()
|
||||
|
||||
if "version" not in doc.attrib:
|
||||
print('Version missing from "doc"')
|
||||
sys.exit(255)
|
||||
|
||||
version = doc.attrib["version"]
|
||||
|
||||
if doc.attrib["name"] in class_names:
|
||||
continue
|
||||
class_names.append(doc.attrib["name"])
|
||||
classes[doc.attrib["name"]] = doc
|
||||
|
||||
class_names.sort()
|
||||
|
||||
if len(input_class_list) < 1:
|
||||
input_class_list = ["*"]
|
||||
|
||||
filtered_classes = set()
|
||||
for pattern in input_class_list:
|
||||
filtered_classes |= set(fnmatch.filter(class_names, pattern))
|
||||
filtered_classes = list(filtered_classes)
|
||||
filtered_classes.sort()
|
||||
|
||||
################################################################################
|
||||
# Make output table #
|
||||
################################################################################
|
||||
|
||||
table = [table_column_names]
|
||||
table_row_chars = "| - "
|
||||
table_column_chars = "|"
|
||||
|
||||
total_status = ClassStatus("Total")
|
||||
|
||||
for cn in filtered_classes:
|
||||
|
||||
c = classes[cn]
|
||||
validate_tag(c, "class")
|
||||
status = ClassStatus.generate_for_class(c)
|
||||
|
||||
total_status = total_status + status
|
||||
|
||||
if (flags["b"] and status.is_ok()) or (flags["g"] and not status.is_ok()) or (not flags["a"]):
|
||||
continue
|
||||
|
||||
if flags["e"] and status.is_empty():
|
||||
continue
|
||||
|
||||
out = status.make_output()
|
||||
row = []
|
||||
for column in table_columns:
|
||||
if column in out:
|
||||
row.append(out[column])
|
||||
else:
|
||||
row.append("")
|
||||
|
||||
if "comment" in out and out["comment"] != "":
|
||||
row.append(out["comment"])
|
||||
|
||||
table.append(row)
|
||||
|
||||
|
||||
################################################################################
|
||||
# Print output table #
|
||||
################################################################################
|
||||
|
||||
if len(table) == 1 and flags["a"]:
|
||||
print(color("part_big_problem", "No classes suitable for printing!"))
|
||||
sys.exit(0)
|
||||
|
||||
if len(table) > 2 or not flags["a"]:
|
||||
total_status.name = "Total = {0}".format(len(table) - 1)
|
||||
out = total_status.make_output()
|
||||
row = []
|
||||
for column in table_columns:
|
||||
if column in out:
|
||||
row.append(out[column])
|
||||
else:
|
||||
row.append("")
|
||||
table.append(row)
|
||||
|
||||
if flags["a"]:
|
||||
# Duplicate the headers at the bottom of the table so they can be viewed
|
||||
# without having to scroll back to the top.
|
||||
table.append(table_column_names)
|
||||
|
||||
table_column_sizes = []
|
||||
for row in table:
|
||||
for cell_i, cell in enumerate(row):
|
||||
if cell_i >= len(table_column_sizes):
|
||||
table_column_sizes.append(0)
|
||||
|
||||
table_column_sizes[cell_i] = max(nonescape_len(cell), table_column_sizes[cell_i])
|
||||
|
||||
divider_string = table_row_chars[0]
|
||||
for cell_i in range(len(table[0])):
|
||||
divider_string += (
|
||||
table_row_chars[1] + table_row_chars[2] * (table_column_sizes[cell_i]) + table_row_chars[1] + table_row_chars[0]
|
||||
)
|
||||
print(divider_string)
|
||||
|
||||
for row_i, row in enumerate(table):
|
||||
row_string = table_column_chars
|
||||
for cell_i, cell in enumerate(row):
|
||||
padding_needed = table_column_sizes[cell_i] - nonescape_len(cell) + 2
|
||||
if cell_i == 0:
|
||||
row_string += table_row_chars[3] + cell + table_row_chars[3] * (padding_needed - 1)
|
||||
else:
|
||||
row_string += (
|
||||
table_row_chars[3] * int(math.floor(float(padding_needed) / 2))
|
||||
+ cell
|
||||
+ table_row_chars[3] * int(math.ceil(float(padding_needed) / 2))
|
||||
)
|
||||
row_string += table_column_chars
|
||||
|
||||
print(row_string)
|
||||
|
||||
# Account for the possible double header (if the `a` flag is enabled).
|
||||
# No need to have a condition for the flag, as this will behave correctly
|
||||
# if the flag is disabled.
|
||||
if row_i == 0 or row_i == len(table) - 3 or row_i == len(table) - 2:
|
||||
print(divider_string)
|
||||
|
||||
print(divider_string)
|
||||
|
||||
if total_status.is_ok() and not flags["g"]:
|
||||
print("All listed classes are " + color("part_good", "OK") + "!")
|
File diff suppressed because it is too large
Load Diff
@ -1,47 +0,0 @@
|
||||
# Makefile providing various facilities to manage translations
|
||||
|
||||
TEMPLATE = classes.pot
|
||||
POFILES = $(wildcard *.po)
|
||||
LANGS = $(POFILES:%.po=%)
|
||||
|
||||
all: update merge
|
||||
|
||||
update:
|
||||
@cd ../..; \
|
||||
python3 doc/translations/extract.py \
|
||||
--path doc/classes modules/*/doc_classes \
|
||||
--output doc/translations/$(TEMPLATE)
|
||||
|
||||
merge:
|
||||
@for po in $(POFILES); do \
|
||||
echo -e "\nMerging $$po..."; \
|
||||
msgmerge -w 79 -C $$po $$po $(TEMPLATE) > "$$po".new; \
|
||||
mv -f "$$po".new $$po; \
|
||||
msgattrib --output-file=$$po --no-obsolete $$po; \
|
||||
done
|
||||
|
||||
check:
|
||||
@for po in $(POFILES); do msgfmt -c $$po -o /dev/null; done
|
||||
|
||||
# Generate completion ratio from statistics string such as:
|
||||
# 2775 translated messages, 272 fuzzy translations, 151 untranslated messages.
|
||||
# First number can be 0, second and third numbers are only present if non-zero.
|
||||
include-list:
|
||||
@list=""; \
|
||||
threshold=0.10; \
|
||||
exclude_ctl="ar bn fa he hi ml si ta te ur"; \
|
||||
for po in $(POFILES); do \
|
||||
lang=`basename $$po .po`; \
|
||||
if `grep -q $$lang <<< $$exclude_ctl`; then continue; fi; \
|
||||
res=`msgfmt --statistics $$po -o /dev/null 2>&1 | sed 's/[^0-9,]*//g'`; \
|
||||
complete=`cut -d',' -f1 <<< $$res`; \
|
||||
fuzzy_or_untranslated=`cut -d',' -f2 <<< $$res`; \
|
||||
untranslated_maybe=`cut -d',' -f3 <<< $$res`; \
|
||||
if [ -z "$$fuzzy_or_untranslated" ]; then fuzzy_or_untranslated=0; fi; \
|
||||
if [ -z "$$untranslated_maybe" ]; then untranslated_maybe=0; fi; \
|
||||
incomplete=`expr $$fuzzy_or_untranslated + $$untranslated_maybe`; \
|
||||
if `awk "BEGIN {exit !($$complete / ($$complete + $$incomplete) > $$threshold)}"`; then \
|
||||
list+="$$lang,"; \
|
||||
fi; \
|
||||
done; \
|
||||
echo $$list;
|
@ -1 +0,0 @@
|
||||
These `.po` and `.pot` files come from Weblate. Do not modify them manually.
|
76101
doc/translations/ar.po
76101
doc/translations/ar.po
File diff suppressed because it is too large
Load Diff
75768
doc/translations/ca.po
75768
doc/translations/ca.po
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
76508
doc/translations/cs.po
76508
doc/translations/cs.po
File diff suppressed because it is too large
Load Diff
80397
doc/translations/de.po
80397
doc/translations/de.po
File diff suppressed because it is too large
Load Diff
75813
doc/translations/el.po
75813
doc/translations/el.po
File diff suppressed because it is too large
Load Diff
98948
doc/translations/es.po
98948
doc/translations/es.po
File diff suppressed because it is too large
Load Diff
75658
doc/translations/et.po
75658
doc/translations/et.po
File diff suppressed because it is too large
Load Diff
@ -1,306 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import shutil
|
||||
from collections import OrderedDict
|
||||
|
||||
EXTRACT_TAGS = ["description", "brief_description", "member", "constant", "theme_item", "link"]
|
||||
HEADER = """\
|
||||
# LANGUAGE translation of the Godot Engine class reference.
|
||||
# Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md).
|
||||
# Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur.
|
||||
# This file is distributed under the same license as the Godot source code.
|
||||
#
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||
#
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Godot Engine class reference\\n"
|
||||
"Report-Msgid-Bugs-To: https://github.com/godotengine/godot\\n"
|
||||
"MIME-Version: 1.0\\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\\n"
|
||||
"Content-Transfer-Encoding: 8-bit\\n"
|
||||
|
||||
"""
|
||||
# Some strings used by make_rst.py are normally part of the editor translations,
|
||||
# so we need to include them manually here for the online docs.
|
||||
BASE_STRINGS = [
|
||||
"Description",
|
||||
"Tutorials",
|
||||
"Properties",
|
||||
"Methods",
|
||||
"Theme Properties",
|
||||
"Signals",
|
||||
"Enumerations",
|
||||
"Constants",
|
||||
"Property Descriptions",
|
||||
"Method Descriptions",
|
||||
"Theme Property Descriptions",
|
||||
"Inherits:",
|
||||
"Inherited By:",
|
||||
"(overrides %s)",
|
||||
"Default",
|
||||
"Setter",
|
||||
"value",
|
||||
"Getter",
|
||||
"This method should typically be overridden by the user to have any effect.",
|
||||
"This method has no side effects. It doesn't modify any of the instance's member variables.",
|
||||
"This method accepts any number of arguments after the ones described here.",
|
||||
"This method is used to construct a type.",
|
||||
"This method doesn't need an instance to be called, so it can be called directly using the class name.",
|
||||
"This method describes a valid operator to use with this type as left-hand operand.",
|
||||
]
|
||||
|
||||
## <xml-line-number-hack from="https://stackoverflow.com/a/36430270/10846399">
|
||||
import sys
|
||||
|
||||
sys.modules["_elementtree"] = None
|
||||
import xml.etree.ElementTree as ET
|
||||
|
||||
## override the parser to get the line number
|
||||
class LineNumberingParser(ET.XMLParser):
|
||||
def _start(self, *args, **kwargs):
|
||||
## Here we assume the default XML parser which is expat
|
||||
## and copy its element position attributes into output Elements
|
||||
element = super(self.__class__, self)._start(*args, **kwargs)
|
||||
element._start_line_number = self.parser.CurrentLineNumber
|
||||
element._start_column_number = self.parser.CurrentColumnNumber
|
||||
element._start_byte_index = self.parser.CurrentByteIndex
|
||||
return element
|
||||
|
||||
def _end(self, *args, **kwargs):
|
||||
element = super(self.__class__, self)._end(*args, **kwargs)
|
||||
element._end_line_number = self.parser.CurrentLineNumber
|
||||
element._end_column_number = self.parser.CurrentColumnNumber
|
||||
element._end_byte_index = self.parser.CurrentByteIndex
|
||||
return element
|
||||
|
||||
|
||||
## </xml-line-number-hack>
|
||||
|
||||
|
||||
class Desc:
|
||||
def __init__(self, line_no, msg, desc_list=None):
|
||||
## line_no : the line number where the desc is
|
||||
## msg : the description string
|
||||
## desc_list : the DescList it belongs to
|
||||
self.line_no = line_no
|
||||
self.msg = msg
|
||||
self.desc_list = desc_list
|
||||
|
||||
|
||||
class DescList:
|
||||
def __init__(self, doc, path):
|
||||
## doc : root xml element of the document
|
||||
## path : file path of the xml document
|
||||
## list : list of Desc objects for this document
|
||||
self.doc = doc
|
||||
self.path = path
|
||||
self.list = []
|
||||
|
||||
|
||||
def print_error(error):
|
||||
print("ERROR: {}".format(error))
|
||||
|
||||
|
||||
## build classes with xml elements recursively
|
||||
def _collect_classes_dir(path, classes):
|
||||
if not os.path.isdir(path):
|
||||
print_error("Invalid directory path: {}".format(path))
|
||||
exit(1)
|
||||
for _dir in map(lambda dir: os.path.join(path, dir), os.listdir(path)):
|
||||
if os.path.isdir(_dir):
|
||||
_collect_classes_dir(_dir, classes)
|
||||
elif os.path.isfile(_dir):
|
||||
if not _dir.endswith(".xml"):
|
||||
# print("Got non-.xml file '{}', skipping.".format(path))
|
||||
continue
|
||||
_collect_classes_file(_dir, classes)
|
||||
|
||||
|
||||
## opens a file and parse xml add to classes
|
||||
def _collect_classes_file(path, classes):
|
||||
if not os.path.isfile(path) or not path.endswith(".xml"):
|
||||
print_error("Invalid xml file path: {}".format(path))
|
||||
exit(1)
|
||||
print("Collecting file: {}".format(os.path.basename(path)))
|
||||
|
||||
try:
|
||||
tree = ET.parse(path, parser=LineNumberingParser())
|
||||
except ET.ParseError as e:
|
||||
print_error("Parse error reading file '{}': {}".format(path, e))
|
||||
exit(1)
|
||||
|
||||
doc = tree.getroot()
|
||||
|
||||
if "name" in doc.attrib:
|
||||
if "version" not in doc.attrib:
|
||||
print_error("Version missing from 'doc', file: {}".format(path))
|
||||
|
||||
name = doc.attrib["name"]
|
||||
if name in classes:
|
||||
print_error("Duplicate class {} at path {}".format(name, path))
|
||||
exit(1)
|
||||
classes[name] = DescList(doc, path)
|
||||
else:
|
||||
print_error("Unknown XML file {}, skipping".format(path))
|
||||
|
||||
|
||||
## regions are list of tuples with size 3 (start_index, end_index, indent)
|
||||
## indication in string where the codeblock starts, ends, and it's indent
|
||||
## if i inside the region returns the indent, else returns -1
|
||||
def _get_xml_indent(i, regions):
|
||||
for region in regions:
|
||||
if region[0] < i < region[1]:
|
||||
return region[2]
|
||||
return -1
|
||||
|
||||
|
||||
## find and build all regions of codeblock which we need later
|
||||
def _make_codeblock_regions(desc, path=""):
|
||||
code_block_end = False
|
||||
code_block_index = 0
|
||||
code_block_regions = []
|
||||
while not code_block_end:
|
||||
code_block_index = desc.find("[codeblock]", code_block_index)
|
||||
if code_block_index < 0:
|
||||
break
|
||||
xml_indent = 0
|
||||
while True:
|
||||
## [codeblock] always have a trailing new line and some tabs
|
||||
## those tabs are belongs to xml indentations not code indent
|
||||
if desc[code_block_index + len("[codeblock]\n") + xml_indent] == "\t":
|
||||
xml_indent += 1
|
||||
else:
|
||||
break
|
||||
end_index = desc.find("[/codeblock]", code_block_index)
|
||||
if end_index < 0:
|
||||
print_error("Non terminating codeblock: {}".format(path))
|
||||
exit(1)
|
||||
code_block_regions.append((code_block_index, end_index, xml_indent))
|
||||
code_block_index += 1
|
||||
return code_block_regions
|
||||
|
||||
|
||||
def _strip_and_split_desc(desc, code_block_regions):
|
||||
desc_strip = "" ## a stripped desc msg
|
||||
total_indent = 0 ## code indent = total indent - xml indent
|
||||
for i in range(len(desc)):
|
||||
c = desc[i]
|
||||
if c == "\n":
|
||||
c = "\\n"
|
||||
if c == '"':
|
||||
c = '\\"'
|
||||
if c == "\\":
|
||||
c = "\\\\" ## <element \> is invalid for msgmerge
|
||||
if c == "\t":
|
||||
xml_indent = _get_xml_indent(i, code_block_regions)
|
||||
if xml_indent >= 0:
|
||||
total_indent += 1
|
||||
if xml_indent < total_indent:
|
||||
c = "\\t"
|
||||
else:
|
||||
continue
|
||||
else:
|
||||
continue
|
||||
desc_strip += c
|
||||
if c == "\\n":
|
||||
total_indent = 0
|
||||
return desc_strip
|
||||
|
||||
|
||||
## make catalog strings from xml elements
|
||||
def _make_translation_catalog(classes):
|
||||
unique_msgs = OrderedDict()
|
||||
for class_name in classes:
|
||||
desc_list = classes[class_name]
|
||||
for elem in desc_list.doc.iter():
|
||||
if elem.tag in EXTRACT_TAGS:
|
||||
elem_text = elem.text
|
||||
if elem.tag == "link":
|
||||
elem_text = elem.attrib["title"] if "title" in elem.attrib else ""
|
||||
if not elem_text or len(elem_text) == 0:
|
||||
continue
|
||||
|
||||
line_no = elem._start_line_number if elem_text[0] != "\n" else elem._start_line_number + 1
|
||||
desc_str = elem_text.strip()
|
||||
code_block_regions = _make_codeblock_regions(desc_str, desc_list.path)
|
||||
desc_msg = _strip_and_split_desc(desc_str, code_block_regions)
|
||||
desc_obj = Desc(line_no, desc_msg, desc_list)
|
||||
desc_list.list.append(desc_obj)
|
||||
|
||||
if desc_msg not in unique_msgs:
|
||||
unique_msgs[desc_msg] = [desc_obj]
|
||||
else:
|
||||
unique_msgs[desc_msg].append(desc_obj)
|
||||
return unique_msgs
|
||||
|
||||
|
||||
## generate the catalog file
|
||||
def _generate_translation_catalog_file(unique_msgs, output, location_line=False):
|
||||
with open(output, "w", encoding="utf8") as f:
|
||||
f.write(HEADER)
|
||||
for msg in BASE_STRINGS:
|
||||
f.write("#: doc/tools/make_rst.py\n")
|
||||
f.write('msgid "{}"\n'.format(msg))
|
||||
f.write('msgstr ""\n\n')
|
||||
for msg in unique_msgs:
|
||||
if len(msg) == 0 or msg in BASE_STRINGS:
|
||||
continue
|
||||
|
||||
f.write("#:")
|
||||
desc_list = unique_msgs[msg]
|
||||
for desc in desc_list:
|
||||
path = desc.desc_list.path.replace("\\", "/")
|
||||
if path.startswith("./"):
|
||||
path = path[2:]
|
||||
if location_line: # Can be skipped as diffs on line numbers are spammy.
|
||||
f.write(" {}:{}".format(path, desc.line_no))
|
||||
else:
|
||||
f.write(" {}".format(path))
|
||||
f.write("\n")
|
||||
|
||||
f.write('msgid "{}"\n'.format(msg))
|
||||
f.write('msgstr ""\n\n')
|
||||
|
||||
## TODO: what if 'nt'?
|
||||
if os.name == "posix":
|
||||
print("Wrapping template at 79 characters for compatibility with Weblate.")
|
||||
os.system("msgmerge -w79 {0} {0} > {0}.wrap".format(output))
|
||||
shutil.move("{}.wrap".format(output), output)
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument(
|
||||
"--path", "-p", nargs="+", default=".", help="The directory or directories containing XML files to collect."
|
||||
)
|
||||
parser.add_argument("--output", "-o", default="translation_catalog.pot", help="The path to the output file.")
|
||||
args = parser.parse_args()
|
||||
|
||||
output = os.path.abspath(args.output)
|
||||
if not os.path.isdir(os.path.dirname(output)) or not output.endswith(".pot"):
|
||||
print_error("Invalid output path: {}".format(output))
|
||||
exit(1)
|
||||
|
||||
classes = OrderedDict()
|
||||
for path in args.path:
|
||||
if not os.path.isdir(path):
|
||||
print_error("Invalid working directory path: {}".format(path))
|
||||
exit(1)
|
||||
|
||||
print("\nCurrent working dir: {}".format(path))
|
||||
|
||||
path_classes = OrderedDict() ## dictionary of key=class_name, value=DescList objects
|
||||
_collect_classes_dir(path, path_classes)
|
||||
classes.update(path_classes)
|
||||
|
||||
classes = OrderedDict(sorted(classes.items(), key=lambda kv: kv[0].lower()))
|
||||
unique_msgs = _make_translation_catalog(classes)
|
||||
_generate_translation_catalog_file(unique_msgs, output)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
76119
doc/translations/fa.po
76119
doc/translations/fa.po
File diff suppressed because it is too large
Load Diff
75919
doc/translations/fi.po
75919
doc/translations/fi.po
File diff suppressed because it is too large
Load Diff
75664
doc/translations/fil.po
75664
doc/translations/fil.po
File diff suppressed because it is too large
Load Diff
94078
doc/translations/fr.po
94078
doc/translations/fr.po
File diff suppressed because it is too large
Load Diff
75653
doc/translations/gl.po
75653
doc/translations/gl.po
File diff suppressed because it is too large
Load Diff
75652
doc/translations/hi.po
75652
doc/translations/hi.po
File diff suppressed because it is too large
Load Diff
75674
doc/translations/hu.po
75674
doc/translations/hu.po
File diff suppressed because it is too large
Load Diff
76142
doc/translations/id.po
76142
doc/translations/id.po
File diff suppressed because it is too large
Load Diff
75652
doc/translations/is.po
75652
doc/translations/is.po
File diff suppressed because it is too large
Load Diff
77286
doc/translations/it.po
77286
doc/translations/it.po
File diff suppressed because it is too large
Load Diff
79931
doc/translations/ja.po
79931
doc/translations/ja.po
File diff suppressed because it is too large
Load Diff
76263
doc/translations/ko.po
76263
doc/translations/ko.po
File diff suppressed because it is too large
Load Diff
75662
doc/translations/lt.po
75662
doc/translations/lt.po
File diff suppressed because it is too large
Load Diff
75670
doc/translations/lv.po
75670
doc/translations/lv.po
File diff suppressed because it is too large
Load Diff
75650
doc/translations/mr.po
75650
doc/translations/mr.po
File diff suppressed because it is too large
Load Diff
75662
doc/translations/nb.po
75662
doc/translations/nb.po
File diff suppressed because it is too large
Load Diff
75650
doc/translations/ne.po
75650
doc/translations/ne.po
File diff suppressed because it is too large
Load Diff
75724
doc/translations/nl.po
75724
doc/translations/nl.po
File diff suppressed because it is too large
Load Diff
76541
doc/translations/pl.po
76541
doc/translations/pl.po
File diff suppressed because it is too large
Load Diff
76952
doc/translations/pt.po
76952
doc/translations/pt.po
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
75686
doc/translations/ro.po
75686
doc/translations/ro.po
File diff suppressed because it is too large
Load Diff
79505
doc/translations/ru.po
79505
doc/translations/ru.po
File diff suppressed because it is too large
Load Diff
75656
doc/translations/sk.po
75656
doc/translations/sk.po
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
75654
doc/translations/sv.po
75654
doc/translations/sv.po
File diff suppressed because it is too large
Load Diff
75957
doc/translations/th.po
75957
doc/translations/th.po
File diff suppressed because it is too large
Load Diff
75810
doc/translations/tl.po
75810
doc/translations/tl.po
File diff suppressed because it is too large
Load Diff
76678
doc/translations/tr.po
76678
doc/translations/tr.po
File diff suppressed because it is too large
Load Diff
76056
doc/translations/uk.po
76056
doc/translations/uk.po
File diff suppressed because it is too large
Load Diff
76351
doc/translations/vi.po
76351
doc/translations/vi.po
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
26
editor/SCsub
26
editor/SCsub
@ -69,32 +69,6 @@ if env["tools"]:
|
||||
env.Depends("#editor/doc_data_compressed.gen.h", docs)
|
||||
env.CommandNoCache("#editor/doc_data_compressed.gen.h", docs, run_in_subprocess(editor_builders.make_doc_header))
|
||||
|
||||
# Editor interface and class reference translations incur a significant size
|
||||
# cost for the editor binary (see godot-proposals#3421).
|
||||
# To limit it, we only include translations with a high enough completion
|
||||
# ratio (30% for the editor UI, 10% for the class reference).
|
||||
# Generated with `make include-list` for each resource.
|
||||
# Note: In 3.x, we also exclude languages that depend on complex text
|
||||
# layouts to be displayed properly: ar,bn,fa,he,hi,ml,si,ta,te,ur.
|
||||
|
||||
# Editor translations
|
||||
to_include = (
|
||||
"bg,ca,cs,de,el,eo,es_AR,es,fi,fr,gl,hu,id,it,ja,ko,lv,ms,nb,nl,pl,pt_BR,pt,ro,ru,sk,sv,th,tr,uk,vi,zh_CN,zh_TW"
|
||||
).split(",")
|
||||
tlist = [env.Dir("#editor/translations").abspath + "/" + f + ".po" for f in to_include]
|
||||
env.Depends("#editor/editor_translations.gen.h", tlist)
|
||||
env.CommandNoCache(
|
||||
"#editor/editor_translations.gen.h", tlist, run_in_subprocess(editor_builders.make_editor_translations_header)
|
||||
)
|
||||
|
||||
# Documentation translations
|
||||
to_include = "de,es,fr,hu,ja,zh_CN".split(",")
|
||||
tlist = [env.Dir("#doc/translations").abspath + "/" + f + ".po" for f in to_include]
|
||||
env.Depends("#editor/doc_translations.gen.h", tlist)
|
||||
env.CommandNoCache(
|
||||
"#editor/doc_translations.gen.h", tlist, run_in_subprocess(editor_builders.make_doc_translations_header)
|
||||
)
|
||||
|
||||
# Fonts
|
||||
flist = glob.glob(env.Dir("#thirdparty").abspath + "/fonts/*.ttf")
|
||||
flist.extend(glob.glob(env.Dir("#thirdparty").abspath + "/fonts/*.otf"))
|
||||
|
@ -78,93 +78,5 @@ def make_fonts_header(target, source, env):
|
||||
g.close()
|
||||
|
||||
|
||||
def make_translations_header(target, source, env, category):
|
||||
|
||||
dst = target[0]
|
||||
|
||||
g = open_utf8(dst, "w")
|
||||
|
||||
g.write("/* THIS FILE IS GENERATED DO NOT EDIT */\n")
|
||||
g.write("#ifndef _{}_TRANSLATIONS_H\n".format(category.upper()))
|
||||
g.write("#define _{}_TRANSLATIONS_H\n".format(category.upper()))
|
||||
|
||||
import zlib
|
||||
import os.path
|
||||
|
||||
sorted_paths = sorted(source, key=lambda path: os.path.splitext(os.path.basename(path))[0])
|
||||
|
||||
msgfmt_available = shutil.which("msgfmt") is not None
|
||||
|
||||
if not msgfmt_available:
|
||||
print("WARNING: msgfmt is not found, using .po files instead of .mo")
|
||||
|
||||
xl_names = []
|
||||
for i in range(len(sorted_paths)):
|
||||
if msgfmt_available:
|
||||
mo_path = os.path.join(tempfile.gettempdir(), uuid.uuid4().hex + ".mo")
|
||||
cmd = "msgfmt " + sorted_paths[i] + " --no-hash -o " + mo_path
|
||||
try:
|
||||
subprocess.Popen(cmd, shell=True, stderr=subprocess.PIPE).communicate()
|
||||
with open(mo_path, "rb") as f:
|
||||
buf = f.read()
|
||||
except OSError as e:
|
||||
print(
|
||||
"WARNING: msgfmt execution failed, using .po file instead of .mo: path=%r; [%s] %s"
|
||||
% (sorted_paths[i], e.__class__.__name__, e)
|
||||
)
|
||||
with open(sorted_paths[i], "rb") as f:
|
||||
buf = f.read()
|
||||
finally:
|
||||
try:
|
||||
os.remove(mo_path)
|
||||
except OSError as e:
|
||||
# Do not fail the entire build if it cannot delete a temporary file
|
||||
print(
|
||||
"WARNING: Could not delete temporary .mo file: path=%r; [%s] %s"
|
||||
% (mo_path, e.__class__.__name__, e)
|
||||
)
|
||||
else:
|
||||
with open(sorted_paths[i], "rb") as f:
|
||||
buf = f.read()
|
||||
|
||||
decomp_size = len(buf)
|
||||
buf = zlib.compress(buf)
|
||||
name = os.path.splitext(os.path.basename(sorted_paths[i]))[0]
|
||||
|
||||
g.write("static const unsigned char _{}_translation_{}_compressed[] = {{\n".format(category, name))
|
||||
for j in range(len(buf)):
|
||||
g.write("\t" + byte_to_str(buf[j]) + ",\n")
|
||||
|
||||
g.write("};\n")
|
||||
|
||||
xl_names.append([name, len(buf), str(decomp_size)])
|
||||
|
||||
g.write("struct {}TranslationList {{\n".format(category.capitalize()))
|
||||
g.write("\tconst char* lang;\n")
|
||||
g.write("\tint comp_size;\n")
|
||||
g.write("\tint uncomp_size;\n")
|
||||
g.write("\tconst unsigned char* data;\n")
|
||||
g.write("};\n\n")
|
||||
g.write("static {}TranslationList _{}_translations[] = {{\n".format(category.capitalize(), category))
|
||||
for x in xl_names:
|
||||
g.write(
|
||||
'\t{{ "{}", {}, {}, _{}_translation_{}_compressed }},\n'.format(x[0], str(x[1]), str(x[2]), category, x[0])
|
||||
)
|
||||
g.write("\t{NULL, 0, 0, NULL}\n")
|
||||
g.write("};\n")
|
||||
|
||||
g.write("#endif")
|
||||
|
||||
g.close()
|
||||
|
||||
|
||||
def make_editor_translations_header(target, source, env):
|
||||
make_translations_header(target, source, env, "editor")
|
||||
|
||||
|
||||
def make_doc_translations_header(target, source, env):
|
||||
make_translations_header(target, source, env, "doc")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
subprocess_main(globals())
|
||||
|
@ -1,44 +0,0 @@
|
||||
# Makefile providing various facilities to manage translations
|
||||
|
||||
TEMPLATE = editor.pot
|
||||
POFILES = $(wildcard *.po)
|
||||
LANGS = $(POFILES:%.po=%)
|
||||
|
||||
all: update merge
|
||||
|
||||
update:
|
||||
@cd ../..; python3 editor/translations/extract.py
|
||||
|
||||
merge:
|
||||
@for po in $(POFILES); do \
|
||||
echo -e "\nMerging $$po..."; \
|
||||
msgmerge -w 79 -C $$po $$po $(TEMPLATE) > "$$po".new; \
|
||||
mv -f "$$po".new $$po; \
|
||||
msgattrib --output-file=$$po --no-obsolete $$po; \
|
||||
done
|
||||
|
||||
check:
|
||||
@for po in $(POFILES); do msgfmt -c $$po -o /dev/null; done
|
||||
|
||||
# Generate completion ratio from statistics string such as:
|
||||
# 2775 translated messages, 272 fuzzy translations, 151 untranslated messages.
|
||||
# First number can be 0, second and third numbers are only present if non-zero.
|
||||
include-list:
|
||||
@list=""; \
|
||||
threshold=0.20; \
|
||||
exclude_ctl="ar bn fa he hi ml si ta te ur"; \
|
||||
for po in $(POFILES); do \
|
||||
lang=`basename $$po .po`; \
|
||||
if `grep -q $$lang <<< $$exclude_ctl`; then continue; fi; \
|
||||
res=`msgfmt --statistics $$po -o /dev/null 2>&1 | sed 's/[^0-9,]*//g'`; \
|
||||
complete=`cut -d',' -f1 <<< $$res`; \
|
||||
fuzzy_or_untranslated=`cut -d',' -f2 <<< $$res`; \
|
||||
untranslated_maybe=`cut -d',' -f3 <<< $$res`; \
|
||||
if [ -z "$$fuzzy_or_untranslated" ]; then fuzzy_or_untranslated=0; fi; \
|
||||
if [ -z "$$untranslated_maybe" ]; then untranslated_maybe=0; fi; \
|
||||
incomplete=`expr $$fuzzy_or_untranslated + $$untranslated_maybe`; \
|
||||
if `awk "BEGIN {exit !($$complete / ($$complete + $$incomplete) > $$threshold)}"`; then \
|
||||
list+="$$lang,"; \
|
||||
fi; \
|
||||
done; \
|
||||
echo $$list;
|
@ -1,23 +0,0 @@
|
||||
# How to contribute translations
|
||||
|
||||
Godot's translation work is coordinated on
|
||||
[Hosted Weblate](https://hosted.weblate.org/projects/godot-engine/godot),
|
||||
an open source web-based translation platform, where contributors can work
|
||||
together on translations using various internationalization features.
|
||||
Creating an account there is free, and you can also login directly with
|
||||
your GitHub, BitBucket, Google or Facebook account.
|
||||
|
||||
To avoid merge conflicts when syncing translations from Weblate (currently
|
||||
this is done manually), we ask all contributors to work there instead of
|
||||
making pull requests on this repository.
|
||||
|
||||
Link if you missed it: https://hosted.weblate.org/projects/godot-engine/godot
|
||||
|
||||
## Adding new languages
|
||||
|
||||
If you want to translate for a language which is not featured yet on Weblate,
|
||||
you can add it (when logged in) by clicking the "Start new translation"
|
||||
button at the bottom of the page.
|
||||
|
||||
Alternatively, you can use this
|
||||
[direct link](https://hosted.weblate.org/new-lang/godot-engine/godot/).
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -1,298 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import enum
|
||||
import fnmatch
|
||||
import os
|
||||
import os.path
|
||||
import re
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
|
||||
class Message:
|
||||
__slots__ = ("msgid", "msgctxt", "comments", "locations")
|
||||
|
||||
def format(self):
|
||||
lines = []
|
||||
|
||||
if self.comments:
|
||||
for i, content in enumerate(self.comments):
|
||||
prefix = "#. TRANSLATORS:" if i == 0 else "#."
|
||||
lines.append(prefix + content)
|
||||
|
||||
lines.append("#: " + " ".join(self.locations))
|
||||
|
||||
if self.msgctxt:
|
||||
lines.append('msgctxt "{}"'.format(self.msgctxt))
|
||||
|
||||
lines += [
|
||||
'msgid "{}"'.format(self.msgid),
|
||||
'msgstr ""',
|
||||
]
|
||||
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
messages_map = {} # (id, context) -> Message.
|
||||
|
||||
line_nb = False
|
||||
|
||||
for arg in sys.argv[1:]:
|
||||
if arg == "--with-line-nb":
|
||||
print("Enabling line numbers in the context locations.")
|
||||
line_nb = True
|
||||
else:
|
||||
os.sys.exit("Non supported argument '" + arg + "'. Aborting.")
|
||||
|
||||
|
||||
if not os.path.exists("editor"):
|
||||
os.sys.exit("ERROR: This script should be started from the root of the git repo.")
|
||||
|
||||
|
||||
matches = []
|
||||
for root, dirnames, filenames in os.walk("."):
|
||||
dirnames[:] = [d for d in dirnames if d not in ["thirdparty"]]
|
||||
for filename in fnmatch.filter(filenames, "*.cpp"):
|
||||
matches.append(os.path.join(root, filename))
|
||||
for filename in fnmatch.filter(filenames, "*.h"):
|
||||
matches.append(os.path.join(root, filename))
|
||||
matches.sort()
|
||||
|
||||
|
||||
remaps = {}
|
||||
remap_re = re.compile(r'^\t*capitalize_string_remaps\["(?P<from>.+)"\] = (String::utf8\()?"(?P<to>.+)"')
|
||||
stop_words = set()
|
||||
stop_words_re = re.compile(r'^\t*stop_words\.push_back\("(?P<word>.+)"\)')
|
||||
with open("editor/editor_property_name_processor.cpp") as f:
|
||||
for line in f:
|
||||
m = remap_re.search(line)
|
||||
if m:
|
||||
remaps[m.group("from")] = m.group("to")
|
||||
else:
|
||||
m = stop_words_re.search(line)
|
||||
if m:
|
||||
stop_words.add(m.group("word"))
|
||||
|
||||
|
||||
main_po = """
|
||||
# LANGUAGE translation of the Godot Engine editor.
|
||||
# Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md).
|
||||
# Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur.
|
||||
# This file is distributed under the same license as the Godot source code.
|
||||
#
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||
#
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Godot Engine editor\\n"
|
||||
"Report-Msgid-Bugs-To: https://github.com/godotengine/godot\\n"
|
||||
"MIME-Version: 1.0\\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\\n"
|
||||
"Content-Transfer-Encoding: 8-bit\\n"\n
|
||||
"""
|
||||
|
||||
|
||||
class ExtractType(enum.IntEnum):
|
||||
TEXT = 1
|
||||
PROPERTY_PATH = 2
|
||||
GROUP = 3
|
||||
|
||||
|
||||
# Regex "(?P<name>([^"\\]|\\.)*)" creates a group named `name` that matches a string.
|
||||
message_patterns = {
|
||||
re.compile(r'RTR\("(?P<message>([^"\\]|\\.)*)"\)'): ExtractType.TEXT,
|
||||
re.compile(r'TTR\("(?P<message>([^"\\]|\\.)*)"(, "(?P<context>([^"\\]|\\.)*)")?\)'): ExtractType.TEXT,
|
||||
re.compile(r'TTRC\("(?P<message>([^"\\]|\\.)*)"\)'): ExtractType.TEXT,
|
||||
re.compile(r'_initial_set\("(?P<message>[^"]+?)",'): ExtractType.PROPERTY_PATH,
|
||||
re.compile(r'GLOBAL_DEF(_RST)?(_NOVAL)?\("(?P<message>[^"]+?)",'): ExtractType.PROPERTY_PATH,
|
||||
re.compile(r'EDITOR_DEF(_RST)?\("(?P<message>[^"]+?)",'): ExtractType.PROPERTY_PATH,
|
||||
re.compile(
|
||||
r"(ADD_PROPERTYI?|ImportOption|ExportOption)\(PropertyInfo\("
|
||||
+ r"Variant::[_A-Z0-9]+" # Name
|
||||
+ r', "(?P<message>[^"]+)"' # Type
|
||||
+ r'(, [_A-Z0-9]+(, "([^"\\]|\\.)*"(, (?P<usage>[_A-Z0-9]+))?)?|\))' # [, hint[, hint string[, usage]]].
|
||||
): ExtractType.PROPERTY_PATH,
|
||||
re.compile(
|
||||
r"(?!#define )LIMPL_PROPERTY(_RANGE)?\(Variant::[_A-Z0-9]+, (?P<message>[^,]+?),"
|
||||
): ExtractType.PROPERTY_PATH,
|
||||
re.compile(r'(ADD_GROUP|GNAME)\("(?P<message>[^"]+)", "(?P<prefix>[^"]*)"\)'): ExtractType.GROUP,
|
||||
re.compile(r'PNAME\("(?P<message>[^"]+)"\)'): ExtractType.PROPERTY_PATH,
|
||||
}
|
||||
theme_property_patterns = {
|
||||
re.compile(r'set_(constant|font|stylebox|color|icon)\("(?P<message>[^"]+)", '): ExtractType.PROPERTY_PATH,
|
||||
}
|
||||
|
||||
|
||||
# See String::camelcase_to_underscore().
|
||||
capitalize_re = re.compile(r"(?<=\D)(?=\d)|(?<=\d)(?=\D([a-z]|\d))")
|
||||
|
||||
|
||||
def _process_editor_string(name):
|
||||
# See EditorPropertyNameProcessor::process_string().
|
||||
capitalized_parts = []
|
||||
parts = list(filter(bool, name.split("_"))) # Non-empty only.
|
||||
for i, segment in enumerate(parts):
|
||||
if i > 0 and i + 1 < len(parts) and segment in stop_words:
|
||||
capitalized_parts.append(segment)
|
||||
continue
|
||||
|
||||
remapped = remaps.get(segment)
|
||||
if remapped:
|
||||
capitalized_parts.append(remapped)
|
||||
else:
|
||||
# See String::capitalize().
|
||||
# fmt: off
|
||||
capitalized_parts.append(" ".join(
|
||||
part.title()
|
||||
for part in capitalize_re.sub("_", segment).replace("_", " ").split()
|
||||
))
|
||||
# fmt: on
|
||||
|
||||
return " ".join(capitalized_parts)
|
||||
|
||||
|
||||
def _is_block_translator_comment(translator_line):
|
||||
line = translator_line.strip()
|
||||
if line.find("//") == 0:
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
|
||||
|
||||
def _extract_translator_comment(line, is_block_translator_comment):
|
||||
line = line.strip()
|
||||
reached_end = False
|
||||
extracted_comment = ""
|
||||
|
||||
start = line.find("TRANSLATORS:")
|
||||
if start == -1:
|
||||
start = 0
|
||||
else:
|
||||
start += len("TRANSLATORS:")
|
||||
|
||||
if is_block_translator_comment:
|
||||
# If '*/' is found, then it's the end.
|
||||
if line.rfind("*/") != -1:
|
||||
extracted_comment = line[start : line.rfind("*/")]
|
||||
reached_end = True
|
||||
else:
|
||||
extracted_comment = line[start:]
|
||||
else:
|
||||
# If beginning is not '//', then it's the end.
|
||||
if line.find("//") != 0:
|
||||
reached_end = True
|
||||
else:
|
||||
start = 2 if start == 0 else start
|
||||
extracted_comment = line[start:]
|
||||
|
||||
return (not reached_end, extracted_comment)
|
||||
|
||||
|
||||
def process_file(f, fname):
|
||||
l = f.readline()
|
||||
lc = 1
|
||||
reading_translator_comment = False
|
||||
is_block_translator_comment = False
|
||||
translator_comment = ""
|
||||
current_group = ""
|
||||
|
||||
patterns = message_patterns
|
||||
if os.path.basename(fname) == "default_theme.cpp":
|
||||
patterns = {**message_patterns, **theme_property_patterns}
|
||||
|
||||
while l:
|
||||
|
||||
# Detect translator comments.
|
||||
if not reading_translator_comment and l.find("TRANSLATORS:") != -1:
|
||||
reading_translator_comment = True
|
||||
is_block_translator_comment = _is_block_translator_comment(l)
|
||||
translator_comment = ""
|
||||
|
||||
# Gather translator comments. It will be gathered for the next translation function.
|
||||
if reading_translator_comment:
|
||||
reading_translator_comment, extracted_comment = _extract_translator_comment(l, is_block_translator_comment)
|
||||
if extracted_comment != "":
|
||||
translator_comment += extracted_comment + "\n"
|
||||
if not reading_translator_comment:
|
||||
translator_comment = translator_comment[:-1] # Remove extra \n at the end.
|
||||
|
||||
if not reading_translator_comment:
|
||||
for pattern, extract_type in patterns.items():
|
||||
for m in pattern.finditer(l):
|
||||
location = os.path.relpath(fname).replace("\\", "/")
|
||||
if line_nb:
|
||||
location += ":" + str(lc)
|
||||
|
||||
captures = m.groupdict("")
|
||||
msg = captures.get("message", "")
|
||||
msgctx = captures.get("context", "")
|
||||
|
||||
if extract_type == ExtractType.TEXT:
|
||||
_add_message(msg, msgctx, location, translator_comment)
|
||||
elif extract_type == ExtractType.PROPERTY_PATH:
|
||||
if captures.get("usage") == "PROPERTY_USAGE_NOEDITOR":
|
||||
continue
|
||||
|
||||
if current_group:
|
||||
if msg.startswith(current_group):
|
||||
msg = msg[len(current_group) :]
|
||||
elif current_group.startswith(msg):
|
||||
pass # Keep this as-is. See EditorInspector::update_tree().
|
||||
else:
|
||||
current_group = ""
|
||||
|
||||
if "." in msg: # Strip feature tag.
|
||||
msg = msg.split(".", 1)[0]
|
||||
for part in msg.split("/"):
|
||||
_add_message(_process_editor_string(part), msgctx, location, translator_comment)
|
||||
elif extract_type == ExtractType.GROUP:
|
||||
_add_message(msg, msgctx, location, translator_comment)
|
||||
current_group = captures["prefix"]
|
||||
translator_comment = ""
|
||||
|
||||
l = f.readline()
|
||||
lc += 1
|
||||
|
||||
|
||||
def _add_message(msg, msgctx, location, translator_comment):
|
||||
key = (msg, msgctx)
|
||||
message = messages_map.get(key)
|
||||
if not message:
|
||||
message = Message()
|
||||
message.msgid = msg
|
||||
message.msgctxt = msgctx
|
||||
message.locations = []
|
||||
message.comments = []
|
||||
messages_map[key] = message
|
||||
if location not in message.locations:
|
||||
message.locations.append(location)
|
||||
if translator_comment and translator_comment not in message.comments:
|
||||
message.comments.append(translator_comment)
|
||||
|
||||
|
||||
print("Updating the editor.pot template...")
|
||||
|
||||
for fname in matches:
|
||||
with open(fname, "r", encoding="utf8") as f:
|
||||
process_file(f, fname)
|
||||
|
||||
main_po += "\n\n".join(message.format() for message in messages_map.values())
|
||||
|
||||
with open("editor.pot", "w") as f:
|
||||
f.write(main_po)
|
||||
|
||||
if os.name == "posix":
|
||||
print("Wrapping template at 79 characters for compatibility with Weblate.")
|
||||
os.system("msgmerge -w79 editor.pot editor.pot > editor.pot.wrap")
|
||||
shutil.move("editor.pot.wrap", "editor.pot")
|
||||
|
||||
shutil.move("editor.pot", "editor/translations/editor.pot")
|
||||
|
||||
# TODO: Make that in a portable way, if we care; if not, kudos to Unix users
|
||||
if os.name == "posix":
|
||||
added = subprocess.check_output(r"git diff editor/translations/editor.pot | grep \+msgid | wc -l", shell=True)
|
||||
removed = subprocess.check_output(r"git diff editor/translations/editor.pot | grep \\\-msgid | wc -l", shell=True)
|
||||
print("\n# Template changes compared to the staged status:")
|
||||
print("# Additions: %s msgids.\n# Deletions: %s msgids." % (int(added), int(removed)))
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user