diff --git a/README.md b/README.md index 55d1c1b..0521680 100644 --- a/README.md +++ b/README.md @@ -43,7 +43,9 @@ Skelerealms is designed in such a way where you can ignore or replace most of th ## How do I get started? -Visit the wiki for more details. + +Visit the [documentation](docs/user%20guide/quick_start.md) for a quick start guide. + ## What's the project status? @@ -59,3 +61,4 @@ Please note that the project is in an Alpha state, which means breaking changes - Integrating NetworkGD. - 0.7 - Redesigning the save game system. + - Polish cross-scene navigation. diff --git a/docs/concepts/entities.md b/docs/concepts/entities.md new file mode 100644 index 0000000..b3f1b4b --- /dev/null +++ b/docs/concepts/entities.md @@ -0,0 +1,9 @@ +# Entities and Components + +## Entities + +Anything that needs to have cross-scene persistence must be an *Entity*. These are roughly equivalent to CE's *Actors*. An *Entity* is defined as a tree of nodes descending from an `SKEntity` node. During runtime, these entities will live underneath the `SKEntityManager`, which keeps track of what entity goes in what scene, among other things. + +## Components + +*Entites* are largely made up of *Components*. These are nodes that derive from `SKEntityComponent`, special nodes that have built-in management functions. diff --git a/docs/concepts/worlds.md b/docs/concepts/worlds.md new file mode 100644 index 0000000..b60448f --- /dev/null +++ b/docs/concepts/worlds.md @@ -0,0 +1,9 @@ +# Worlds + +*Worlds* are simply a way of expressing what scene an entity belongs to. This is roughly equivalent to CE's *Cells*. + +A scene becoming a World has two requirements: +- The root node (It should be a Node3D but it doesn't have to) has the name of the world +- The scene file is saved in the directory defined by `skelerealms/worlds_path` - by default, `res://worlds` (It can be in a subforlder for organization). The name of the file should exactly match the name of the root node. + +That's it! diff --git a/docs/intro.md b/docs/intro.md new file mode 100644 index 0000000..6158902 --- /dev/null +++ b/docs/intro.md @@ -0,0 +1,53 @@ +# The Skelerealms Grimoire +#### For Skelerealms Beta 0.6 + +## Introduction + +Welcome! There are the high-level docs for Skelerealms, showing you how to use this addon. If you want methods and variables documentation, documentation comments are provided in-engine, like any other class. + +### What is Skelerealms? + +Skelerealms is an addon for Godot 4.2+ that aims to provide the foundation for creating a Bethesda-style Open World RPG (The Elder Scrolls, Fallout, Starfield). No gameplay is provided, but a lot of the challenging systems are in place to allow you to focus on telling your story. + +Skelerealms is inpsired by Creation Engine, but aims to tackle many of its shortcomings with the benefit of 20+ years of hindsight. It's also been designed not to lock you in to any specific way of designing your game - most components are purely optional. You don't even need to have a player to run the game (but I'm not sure what the point of that would be...) + +### What problems does it solve? + +- Cross-scene persistence: If the player drops a book in one room, goes outside, and then comes back inside, that book should still be lying on the floor. This problem is much trickier to solve than it seems; but you don't need to worry about it. Skelerealms has got you covered! +- Cross-scene navigation: If you're running away from an enemy and duck into a building, it only makes sense that the enemy should follow you through the door. This, again, is a tricky problem to solve. Again, though, Skelerealms has solved this problem already, by introducing a supplementary navigation system that exists, no matter what scene is loaded. +- NPC AI: Skelerealms comes with a robust AI system that hopes to smooth out some of the awkwardness of Bethesda's infamous NPCs by using a GOAP (Goal-Orientated Action Planning) system that's also integrated (but not coupled to) a built-in faction and schedule system. +- And much more! + +### Main Features + +- Cross-scene persistence +- Inter-scene navigation +- GOAP +- Inventory +- Status effects and spells +- Loot tables +- Equipment +- Composable item behaviors +- Bartering system +- Factions +- Schedules +- Sight/stealth mechanics + +## Table of Contents + +### Concepts + +### User guide + + +## Project Status + +### Development + +Despite what it may look like on the main repo's commit history, development is ongoing (in the submodule repository.) Skelerelams was originally developed for a game I am making myself, and so I am dog-fooding Skelerealms during the development of this game. As I encounter problems during the development process, I make changes and fixes, and push them upstream. + +### Places to improve + +- The Savegame system needs to be rethought. +- The way the navigation system is represented in code could be much more memory-efficient. I may end up rewriting it in Zig or something. +- Lots of more processing-heavy parts of code should probably be written in a compiled language. diff --git a/docs/user guide/ai_modules.md b/docs/user guide/ai_modules.md new file mode 100644 index 0000000..e8078b6 --- /dev/null +++ b/docs/user guide/ai_modules.md @@ -0,0 +1,8 @@ +# AI Modules + +NPC AI is made of two parts: AI Modules and GOAP Actions. This article covers the AI Modules. AI Modules determine *what* the NPC wants to do, and the GOAP Actions determine *how* to do it. + +AI Modules are rough equivalents to Creation Engine's AI Packages, minus the schedules. They are nodes that are direct children (also see [tools](/docs/user%20guide/tools.md) for `NodeBundler`) of NPCs that inform the NPCs behavior. Without any modules, the NPC will do absolutely nothing. +AI Modules are expected to function largely autonomously from the NPC. The intended design is to hook into one of the NPC's many, many signals and change behavior in response. To get a better idea of how this looks, take a look at some of the exaples that come with Skelerealms, such as [movement](../../scripts/ai/Modules/default_movement.gd) for a simple example, or [threat response](../../scripts/ai/Modules/default_threat_response.gd) for a more complex one. + +As with anything else, code documentation can be found in the in-engine documentation. diff --git a/docs/user guide/components.md b/docs/user guide/components.md new file mode 100644 index 0000000..7331eea --- /dev/null +++ b/docs/user guide/components.md @@ -0,0 +1,47 @@ +# Components + +Entities are designed to be used with Components, which are reusable bits oc foce. Such is the Godot way. +Components aren't particularly special; they simply have a few functions for workign with the entities. + +## Functionality + +- Allows you to specify dependencies, which if it doesn't have, shows up as a warning in the editor +- Virtual functions for generation, spawning, despawning +- Saving, loading +- Gathering debug information +- Printing to console with entity's name, etc. + +## Making your own + +Simply inherit the SKEntityComponent class, and have at it! Just make sure it renames itself to its class name: +the entity's `get_component()` method looks for names, since GDScript has no proper generics. +Also note that you have an easy way to grab the parent entity: `parent_entity`. +If you would like to provide a custom preview scene for manipulating your entity in the editor with SKWorldEntites, +implement a `get_world_entity_preview() -> Node` method that returns a scene you'd like to appear in the editor. + +## Built-in components + +A number of built-in components are offered for your convenience. More in-depth usage of these can be found in their in-engine documentation. + +- Attributes: Covers attributes like Strength, Perception, Endurance, etc. +- Chest: Creates a refilling chest system. Expects a loot table. +- Covens: Integrates with Skelerealms Factions system. +- Damageable: Allows this entity to be damaged. +- Effects: Allows this entity to be subject to the status effects system. +- Equipment: This entity can equip items from an inventory into special equipment slots. +- GOAP: Puts the entity under control of the built-in GOAP system. GOAP Actions come as child nodes. +- Interactive: Allows the player (and others) to interact with this entity. +- Inventory: Gives the entity an inventory, along with management tools. Can use a Loot Table to generate an inventory. Also keeps track of currency. +- Item: This entity is now an item, and can be moved from inventory to inventory. Item Components come as child nodes. +- Marker: This entity can be used as waypoints, or whatever else you need. (I use them as destinations to teleport to in the console.) +- Navigator: Can calculate paths in the granular navigation system. +- NPC: This entity is now an NPC, and has many things it can do as a consequence. AI Modules come as child nodes. +- Player: This entity is a Player. +- Puppet Spawner: Spawns scenes into the game world as "puppets" that are linked to the entity. Used by Items and NPCs to give form to their being. +- Script: Deprecated. +- Shop: This entity can now barter in the barter system. Needs an overhaul. +- Skills: Keeps track of an entity's Skills - One-handed, block, archery, etc. +- Spell Target: Deprecated. Use EffectsComponent instead. +- Teleport: This entity can be teleported. +- View Direction: Keeps track of an entites viewing direction, for stealth mechanics and other such things. +- Vitals: Keeps track of health, stamina, magica, etc. diff --git a/docs/user guide/covens.md b/docs/user guide/covens.md new file mode 100644 index 0000000..cab971f --- /dev/null +++ b/docs/user guide/covens.md @@ -0,0 +1,3 @@ +# Covens + +Covens are Skelerealms' "Factions" system. The odd name is because the game I was making Skelerealms for before deciding to open-source it calls factions "covens", and I never bothered to change it. Covens are an optional system that determine some things about NPC behavior, particularly the crime tracking system and how NPCs determine the opinion they have of something. Many of the covens' attributes are self-explanatory and have further documentation in-editor, but I should add that covens are loaded at game start, and so should be placed in the covens folder in the plugin settings - by default `res://covens`. An entity is added to a coven by adding a `CovensComponent` and adding a `CovenRankData` resource. This may get reworked in the future. diff --git a/docs/user guide/entities.md b/docs/user guide/entities.md new file mode 100644 index 0000000..d9a7b35 --- /dev/null +++ b/docs/user guide/entities.md @@ -0,0 +1,61 @@ +# Entities + +Any scene with SKEntity as the root is considered an Entity. Entities are able to persist between scenes, so use these for everything you want +to stay put when unloaded: NPCs, Items, etc. Entities by themselves do very little - the first layer of children should be made up of SKEntityComponents, +reusable bits of code that inform the behavior of an Entity; for example, keeping track of an inventory. + +## Functionality + +Entities do have some functionality, though; they keep track of the following: + +- Position and rotation of the entity (Note that SKEntity is *not* a Node3D; Making it one does some funky stuff with puppets.) +- The world the entity is in +- Whether the entity should be in the scene or not +- Form ID (used for determining what sort of thing non-unique entities are) +- Whether this entity is unique. + +It provides some hooks for: + +- Spawning, despawning +- Actions called from dialogue +- Generation +- Getting a preview mesh (Components can provide a preview mesh for you to see when placing them in the editor) + +As well as utility functions for: + +- Saving +- Loading +- Gathering debug information (used for debug consoles and the like) +- Managing components + +## Concepts + +The life cycle of an entity goes as follows: +``` +Generation -> Spawning <-> Despawning -> Destruction +``` + +**Spawning** happens every time an entity appears in a scene. This is used for spawning anything +associated with the entity that the player interacts with (referred to as puppets). + +**Generation** is when the entity appears for the first time in a game. Not a game *session*, but a game. This is distinct from spawning +in that spawning occurs every time an entity appears in a game *session*. Further appearances of the entity will be aided by the save game system. +Generation should be used for filling inventories for the first time. + +**Destruction** means the entity will no longer exist in the game, ever. This stage is a work-in-progress, and will probably come in 0.7. + +**Uniqueness** simply means that there will only be one instance of this entity that exists in the game; usually used for named NPC and unique items. +Internally, non-unique entities will be given a randomly-generated RefID upon generation. Non-unique entities should probably be given a FormID. + +## Creating an entity + +Simply create a new scene with an SKEntity as the root, and name it with a RefID if it's unique. The first layer of the tree +under the SKEntity should all be made of SKEntityComponent-derived nodes. Beyond that, that's up to whatever the components expect to be beneath them. +Then, save the entity as a scene file (I prefer `.res`) in the entities path under the `skelerealms/entities_path` project setting; `res://entities` by default. +They can be in a subdirectory for organization. +In the very likely chance that you have many entities with the same general shape, like NPCs, you can have an "archetype" scene that you can inherit entities from, using +the editor's "ingerit scene" functionality. This is handy for saving legwork, as well as changing large swaths of entities at once. Some archetypes are provided in the +Skelerealms folder. +To place individual entities in the world, you can create an SKWorldEntity, and place in an Entity in the inspector. Depending on the entity, you will get a preview. +Manipulate the World Entity to your linkng, then hit "Sync position and world" in the inspector. This will automatically edit the entity to set its position and world +so it spawns where you placed it in the world. diff --git a/docs/user guide/goap_actions.md b/docs/user guide/goap_actions.md new file mode 100644 index 0000000..e0ea147 --- /dev/null +++ b/docs/user guide/goap_actions.md @@ -0,0 +1,27 @@ +# GOAP Actions + +NPC AI is made of two parts: AI Modules and GOAP Actions. This article covers the AI Modules. AI Modules determine *what* the NPC wants to do, and the GOAP Actions determine *how* to do it. + +GOAP Actions are a fair bit more complicated than the AI Modules. If you don't understand how GOAP works in theory, that's outside the scope of this article: search online for more information. + +Again, they are nodes directly beneath a `GOAPComponent`. An actions prerequisites and effects are determined by overriding the `get_prerequisites()` and `get_effects()` functions, returning a dictionary of shape `Effect -> Value`. These are used in the planning process, so shouldn't change during runtime, unless you know what you're doing. The GOAP system works on a timeline divided into two parts, form the perspective of an action: + +## Plan time + +Plan time is simply the time when a plan is created. Every action will have `is_achievable()` called on them. Returning `false`, for any reason you may desire, will exclude them from the planning process. + +## Action time + +Action time is when the action is being executed. The steps of each sequence are given to you as a series of overrideable functions. Returning `false` from any of them will trigger a plan recalculation. The sequence is as follows: + +1. `pre_perform()` - Any actions that should be taken when the action is first begun. Finding targets, triggering animations, etc. +2. `target_reached()` - Something that should happen when a target is reached, determined by the `is_target_reached` function. By default, returns whether a navigation agent has finished navigating to a point. +3. `post_perform()` - Any actions that should be taken once the action is complete. **This happens once `duration` passes after `target_reached()` is called.** + +- `interrupt()` can happen at any time. This detemines what, if anything, should happen if a plan is recalculated mid-action (between `pre_perform()` and `post_perform()`, when `running` is true). This returns nothing. + +--- + +The intended way for GOAP Actions to have an effect on the world is through the given `parent_gop` and `entity` properties, allowing them to, for instance, move an NPC around, or play an animation. + +The planner itself is contained within the `GOAPComponent`, and will attempt to make a plan every frame where it does not have any plan. diff --git a/docs/user guide/loot_tables.md b/docs/user guide/loot_tables.md new file mode 100644 index 0000000..a2796c0 --- /dev/null +++ b/docs/user guide/loot_tables.md @@ -0,0 +1,22 @@ +# Loot Tables + +Skelerealms comes with an in-house loot table system inspired by that seen in Minecraft. It's created using a tree of `SKLootTableItem`s underneath an associated `SKLootTable`. A "resolved" (rolled) loot table creates a result consisting of: + +- Generated items +- Unique items +- Currencies + +Each of these can be manipulated by creating a nested of various `SKLootTableItem`s. Some loot table items will affect the chances or number of child items, while others will instead contribute items to the output. These can be combined in ways to create complex results. [Consider the following](https://www.youtube.com/watch?v=uI_N2tLw-vI) loot table: + +- SKLootTable + - SKLTItem (Contains sword) + - SKLTXOfItem (Set to between 1 and 3) + - SKLTCurrency (Set to between 5 and 20 gold coins) + +This loot table will always return a sword, and then will generate 5 to 20 coins 1 to 3 times. Not the most practical example, but hopefully you get the idea. + +You can put entities you've created (Unique items) into a loot table with `SKLTItemEntity`. Do note, however, that no matter how many times a unique item is rolled, it will only appear in the result once. Non-item entities will appear in the result, but `InventoryComponent` will not add non-item entities into its inventory. Also, the unique items should still be saved and stored within Skelerealms' entities path, as configured in the project settings. + +Built-in, Loot tables are applicable to the following components: +- `InventoryComponent`, which will roll when the entity is `generate`d +- `ChestComponent`, which will roll and fill an inventory whenever the chest refreshes diff --git a/docs/user guide/migrating.md b/docs/user guide/migrating.md new file mode 100644 index 0000000..3b5a475 --- /dev/null +++ b/docs/user guide/migrating.md @@ -0,0 +1,15 @@ +# Migrating from 0.5 to 0.6 + +This will not be easy. Sorry in advance. + +## Instance data + +This will be spotty at best, but a button will appear above `InstanceData`-derived classes that, when pressed, will create a roughly-equivalent Entity, and save it under the same directory. AI-related things and Loot boxes will not be conserved. + +## GOAP + +Luckily, this is easy. Simply change your existing GOAPBehaviors to GOAPActions. Instead of being a property, however, prerequisites and effects have moved into functions to override. + +## Loot tables, schedules + +You will have to remake these from scratch. Sorry. diff --git a/docs/user guide/navigation.md b/docs/user guide/navigation.md new file mode 100644 index 0000000..c3d26c5 --- /dev/null +++ b/docs/user guide/navigation.md @@ -0,0 +1,5 @@ +# Navigation + +TODO + +This is for cross-scene navigation. I might rework the way you do this soon but basically you use the network tool. You can find the repo [here](https://github.com/SlashScreen/godot-network-graph) and look at the wiki for how to use it, but I'm about to merge it into Skelerealms, I think, since having external dependencies creates complications. Anyway, save the network for a world into the networks folder in the project settings, and the system will pick it up into the pathfinding later. Cross-scene navigation is done using the `NavigatorComponent`, although I'm not sure it works right now. diff --git a/docs/user guide/npcs.md b/docs/user guide/npcs.md new file mode 100644 index 0000000..ecd4d8e --- /dev/null +++ b/docs/user guide/npcs.md @@ -0,0 +1,46 @@ +# NPCs + +Skelerealms' NPCs are easily the most complex (and bug-prone - please report any bugs) part of the entire framework. They bring together a number of other complex systems (AI Modules, GOAP, Schedules, Covens, Navigation, Perception) into one entity. You can skip this entire systme and roll your own if you want to. NPCs are a blank slate, and don't do much on their own: their behavior is defined entirely by AI Modules and other systems. + +The component itself offers a number of flags and settings that may define its behavior: + +## Flags + +`essential`, `ghost`, `invulnerable`, `unique`, and `affects_stealth_meter` don't actually do anything by default. They are here because they are often used in your own implementation of gameplay mechanics. + +Interactive, however, determines whther you can interact with this NPC or not, say, to start dialogue. + +## AI + +A few AI settings are here as well, although they are only here to be used by AI Modules. + +- `relationships`: Determines relationships this NPC has with other NPCs, think a father and daughter, a boss and employee, etc. +- `threatening_enemy_types`: The names of components other entities it sees must have for it to determine whether it's a threat or not. It doesn't make much sense for an invisible `Marker` to be a threat, does it? +- `npc_opoinions`: Any opinions it has of any particulat entity, in the shape of `ref_id -> opinion`. For example, an NPC called `biggest_bts_fan` may have an opinion value of 100 of the NPC `jimin`, bringing the opinion calculations up. +- `loyalty`: This determines the "allegiance" on an NPC during opinion calculations; that is, whether the opinion should be weighted more towards the opinions of the NPC's covens or its own loyalties, or no wieght at all. This is only used if the `opinion_mode` is set to `Average`. See "Opinion Calculation". +- `opinion_mode`: How this NPC generates opinions. See "Opinion Calculatin" for details. + + +## Opinion Calculation + +An NPC can determine its own opinion of another NPC. This is used to influence dialogue choices, whether this NPC should attack another NPC, that sort of thing. + +When an NPC calculates an opinion it has on another NPC, influencing its behavior toward the NPC, it has a lot of different things to consider. Opinions are drawn from two sources: + +- Its own opinions defined in `npc_opinions` +- Opinions that each coven the NPC has has regarding each coven the target entity has + +How these opinions factor into the final opinion depends on `opinion_mode`: + +- `Minimum`: The calculated opinion is the minimum of all opinions gathered. This is the default. +- `Maximum`: The calculated opinion is the maximum of all opinions gathered. +- `Average`: The calculated opinion is the weighted average of all the opinions gathered, deduplicated. The weight is determined by `loyalty_mode`. + + +## Simulation Level + +The NPCs have 3 simulation levels to keep down processing power for lots of agents: + +- FULL: Full simulation +- GRANULAR: Partial simulation, only handles schedules and inter-scene navigation +- NONE: The NPC will not do anything. diff --git a/docs/user guide/quick_start.md b/docs/user guide/quick_start.md new file mode 100644 index 0000000..edf015d --- /dev/null +++ b/docs/user guide/quick_start.md @@ -0,0 +1,9 @@ +# Quick Start + +## For New Projects + +Use [the template](https://github.com/SlashScreen/skelerealms-template) project. You now have a basic project! + +## For Existing Projects + +Use this repo as a submodule in your addons folder. diff --git a/docs/user guide/schedules.md b/docs/user guide/schedules.md new file mode 100644 index 0000000..3936488 --- /dev/null +++ b/docs/user guide/schedules.md @@ -0,0 +1,7 @@ +# Schedules + +Skelerealms comes with a schedule/NPC routines system. It's used by adding a `Schedule` node underneath an `NPCComponent`, and adding a number of `ScheduleEvent`s underneath. + +`ScheduleEvents` are nodes that can influence what an NPC does at different times of day, and are roughly equivalent to Creation Engine's "AI Packages" feature, but limited to the "routines" aspect of the feature. This class is meant to be inherited with your own functionality. For an example of how this is done, see the built-in `SandboxSchedule`. The `SandboxSchedule` is perhaps confusingly named, but it is used for NPCs idle behaviors during a set time - for example, milling about in their house during the evening. The "Sandbox" name is inherited from Creation Kit's name for the same idea. + +Most of the properties are documented or self-explanatory. For them to be considered, the current time of day must be between the times described in `from` and `to`. The timestamps can be adjusted to inform to what degree the timestamps must be matched, so NPCs can, for example, do a schedulke every day, or only on certain days. `ScheduleConditions` can also be added to only allow events to happen if certain conditions are met - for example, only having certain behavior happen when a quest is complete. diff --git a/docs/user guide/tools.md b/docs/user guide/tools.md new file mode 100644 index 0000000..097c5b7 --- /dev/null +++ b/docs/user guide/tools.md @@ -0,0 +1,30 @@ +# Tools + +Skelerealms offers a few tools to help with your development. + +## Components + +Components will tell you if any other components they depend on are absent, using the ditor warnings feature. + +## Node Bundles + +If you want to compose a set of AI Modules, Loot table items, etc. the `NodeBundle` class aims to help with that. All it does is take all of its children and reparent them to be siblings of itself, and then removes itself afterwards. Since some systems rely on a tree's structure to determine functionality, this is handy for having a scene you can drag in while making an entity, helping for composability. Simply make a scene with a NodeBundle as a root and all your items below it, and it will flatten out during runtime. + +## SKWorldEntity + +The premiere way to spawn unique entities into the world is by using an SKWorldEntity. Put your entity in, and do with it as you wish. + +There is a work-in-progress tool you can use to create entities based on an archetype (see [entities](entities.md)). You can add paths to each archetype you want to use in the project settings, and then create new ones using the menu in the inspector. It's not perfect, but it will get better support once instancing scenes directly within code gets supported (an active PR). + +You can sync the position of any unique entity by hitting the button in the inspector. This will edit the attached entity to make its position the **global** position of the World Entity node, and set its world to the current edited world (Internally, this is determined to be the name of the root node of the currently edited scene). + +## Doors + +The easiest way to allow the player to move between scenes is the Door. Add a door into the scene. Whenever any entity with a `TeleportComponent` interacts with the door, they will be teleported to the door's other side. To set up a door, do as follows: + +1. Create a Door node, and position it. You can also add any colliders or whatever you use to determine when to interact with something, as well as your mesh instance. +2. Create a resource in the `instance` field, and save it to disk somewhere. press the "Sync position" button to sync the door resource. +3. Create a new door in a different (or the same) scene, and repeat the process. Grab the resource of the door you already have, (or, if you already have a door resource you'd like to link to, grab that instead), and drag it into the `destination_instance` field. You must do this on both sides (a bit tedious, but it allows for more flexible/non-euclidean door layouts). +4. You now have a door. + +At any time, once you have a `destination_instance`, you can press the "Jump to destination" button to navigate the editor's camera to the other side of the door, opening the destination scene in the editor if necessary (providing that the destination world is in the proper folder). diff --git a/item_entity_template.tscn b/item_entity_template.tscn new file mode 100644 index 0000000..fdb9549 --- /dev/null +++ b/item_entity_template.tscn @@ -0,0 +1,18 @@ +[gd_scene load_steps=5 format=3 uid="uid://bt1x4w2k7dhvf"] + +[ext_resource type="Script" path="res://addons/skelerealms/scripts/entities/entity.gd" id="1_mtgwt"] +[ext_resource type="Script" path="res://addons/skelerealms/scripts/components/item_component.gd" id="2_yi40p"] +[ext_resource type="Script" path="res://addons/skelerealms/scripts/components/interactive_component.gd" id="3_lvppr"] +[ext_resource type="Script" path="res://addons/skelerealms/scripts/components/puppet_spawner_component.gd" id="4_nkg75"] + +[node name="npc_item_entity" type="Node"] +script = ExtResource("1_mtgwt") + +[node name="ItemComponent" type="Node" parent="."] +script = ExtResource("2_yi40p") + +[node name="InteractiveComponent" type="Node" parent="."] +script = ExtResource("3_lvppr") + +[node name="PuppetSpawnerComponent" type="Node" parent="."] +script = ExtResource("4_nkg75") diff --git a/npc_entity_template.tscn b/npc_entity_template.tscn new file mode 100644 index 0000000..46650ff --- /dev/null +++ b/npc_entity_template.tscn @@ -0,0 +1,70 @@ +[gd_scene load_steps=18 format=3 uid="uid://dade7o6bx8ja0"] + +[ext_resource type="Script" path="res://addons/skelerealms/scripts/entities/entity.gd" id="1_1v31h"] +[ext_resource type="Script" path="res://addons/skelerealms/scripts/components/npc_component.gd" id="2_macfp"] +[ext_resource type="Script" path="res://addons/skelerealms/scripts/components/interactive_component.gd" id="3_peb1r"] +[ext_resource type="Script" path="res://addons/skelerealms/scripts/components/puppet_spawner_component.gd" id="4_27chb"] +[ext_resource type="Script" path="res://addons/skelerealms/scripts/components/teleport_component.gd" id="5_1ifxk"] +[ext_resource type="Script" path="res://addons/skelerealms/scripts/components/goap_component.gd" id="6_1ul7y"] +[ext_resource type="Script" path="res://addons/skelerealms/scripts/components/skills_component.gd" id="7_mnx3l"] +[ext_resource type="Script" path="res://addons/skelerealms/scripts/components/attributes_component.gd" id="8_vyu84"] +[ext_resource type="Script" path="res://addons/skelerealms/scripts/components/vitals_component.gd" id="9_efn20"] +[ext_resource type="Script" path="res://addons/skelerealms/scripts/components/spell_target_component.gd" id="10_12uph"] +[ext_resource type="Script" path="res://addons/skelerealms/scripts/components/covens_component.gd" id="11_5alr3"] +[ext_resource type="Script" path="res://addons/skelerealms/scripts/components/damageable_component.gd" id="12_oodnq"] +[ext_resource type="Script" path="res://addons/skelerealms/scripts/components/navigator_component.gd" id="13_yt8pj"] +[ext_resource type="Script" path="res://addons/skelerealms/scripts/components/view_direction_component.gd" id="14_2ecvo"] +[ext_resource type="Script" path="res://addons/skelerealms/scripts/components/equipment_component.gd" id="15_6i1jw"] +[ext_resource type="Script" path="res://addons/skelerealms/scripts/components/inventory_component.gd" id="16_27xpq"] +[ext_resource type="Script" path="res://addons/skelerealms/scripts/components/effects_component.gd" id="17_iewsa"] + +[node name="SKEntity" type="Node"] +script = ExtResource("1_1v31h") + +[node name="NPCComponent" type="Node" parent="."] +script = ExtResource("2_macfp") + +[node name="InteractiveComponent" type="Node" parent="."] +script = ExtResource("3_peb1r") + +[node name="PuppetSpawnerComponent" type="Node" parent="."] +script = ExtResource("4_27chb") + +[node name="TeleportComponent" type="Node" parent="."] +script = ExtResource("5_1ifxk") + +[node name="GOAPComponent" type="Node" parent="."] +script = ExtResource("6_1ul7y") + +[node name="SkillsComponent" type="Node" parent="."] +script = ExtResource("7_mnx3l") + +[node name="AttributesComponent" type="Node" parent="."] +script = ExtResource("8_vyu84") + +[node name="VitalsComponent" type="Node" parent="."] +script = ExtResource("9_efn20") + +[node name="SpellTargetComponent" type="Node" parent="."] +script = ExtResource("10_12uph") + +[node name="CovensComponent" type="Node" parent="."] +script = ExtResource("11_5alr3") + +[node name="DamageableComponent" type="Node" parent="."] +script = ExtResource("12_oodnq") + +[node name="NavigatorComponent" type="Node" parent="."] +script = ExtResource("13_yt8pj") + +[node name="ViewDirectionComponent" type="Node" parent="."] +script = ExtResource("14_2ecvo") + +[node name="EquipmentComponent" type="Node" parent="."] +script = ExtResource("15_6i1jw") + +[node name="InventoryComponent" type="Node" parent="."] +script = ExtResource("16_27xpq") + +[node name="EffectsComponent" type="Node" parent="."] +script = ExtResource("17_iewsa") diff --git a/scripts/ai/Modules/default_crime_report.gd b/scripts/ai/Modules/default_crime_report.gd index bfd7b88..6783641 100644 --- a/scripts/ai/Modules/default_crime_report.gd +++ b/scripts/ai/Modules/default_crime_report.gd @@ -1,8 +1,7 @@ -class_name DefaultCrimeReportModule extends AIModule -func _initialize() -> void: +func _ready() -> void: CrimeMaster.crime_committed.connect(react.bind()) diff --git a/scripts/ai/Modules/default_damage_module.gd b/scripts/ai/Modules/default_damage_module.gd index b30799d..e857fba 100644 --- a/scripts/ai/Modules/default_damage_module.gd +++ b/scripts/ai/Modules/default_damage_module.gd @@ -1,4 +1,3 @@ -class_name DefaultDamageModule extends AIModule ## Example implementation of a damage processing AI Module. diff --git a/scripts/ai/Modules/default_interact_response.gd b/scripts/ai/Modules/default_interact_response.gd index 4034d7b..ddcc592 100644 --- a/scripts/ai/Modules/default_interact_response.gd +++ b/scripts/ai/Modules/default_interact_response.gd @@ -1,4 +1,3 @@ -class_name DefaultInteractResponseModule # oh god this is getting java like extends AIModule diff --git a/scripts/ai/Modules/default_movement.gd b/scripts/ai/Modules/default_movement.gd index db4f128..529704e 100644 --- a/scripts/ai/Modules/default_movement.gd +++ b/scripts/ai/Modules/default_movement.gd @@ -1,4 +1,3 @@ -class_name DefaultMovementModule extends AIModule diff --git a/scripts/ai/Modules/default_threat_response.gd b/scripts/ai/Modules/default_threat_response.gd index 02aebe1..1efdcc9 100644 --- a/scripts/ai/Modules/default_threat_response.gd +++ b/scripts/ai/Modules/default_threat_response.gd @@ -1,4 +1,3 @@ -class_name DefaultThreatResponseModule # oh god this is getting java like extends AIModule @@ -46,15 +45,13 @@ var vigilant_thread:Thread var pull_out_of_thread = false -func _initialize() -> void: +func _ready() -> void: _npc.perception_transition.connect(_handle_perception_info.bind()) _npc.hit_by.connect(func(who): _aggress(SKEntityManager.instance.get_entity(who))) func _handle_perception_info(what:StringName, transition:String, fsm:PerceptionFSM_Machine) -> void: var opinion = _npc.determine_opinion_of(what) - _npc.printe("handling perception info on %s" % what) - _npc.printe("Opinion on %s: %s" % [what, opinion]) var below_attack_threshold = (opinion <= attack_threshold) or aggression == 3 # will be below attack threshold by default if frenzied match transition: @@ -188,7 +185,6 @@ func _warn(e:SKEntity) -> void: func _enter_normal_state() -> void: # undo vigilant stance print("exit vigilant stance") - return func _enter_vigilant_stance() -> void: diff --git a/scripts/ai/PerceptionFSM/state_unaware.gd b/scripts/ai/PerceptionFSM/state_unaware.gd index 52ba1bf..2aac1af 100644 --- a/scripts/ai/PerceptionFSM/state_unaware.gd +++ b/scripts/ai/PerceptionFSM/state_unaware.gd @@ -15,7 +15,6 @@ func _get_state_name() -> String: func on_ready() -> void: _npc = owner as NPCComponent - print(_npc) func update(delta:float) -> void: diff --git a/scripts/ai/ai_module.gd b/scripts/ai/ai_module.gd index 4900d78..0ebe5a6 100644 --- a/scripts/ai/ai_module.gd +++ b/scripts/ai/ai_module.gd @@ -1,13 +1,13 @@ @tool class_name AIModule -extends Resource +extends Node ## Base class for AI Packages for NPCs. ## Skelerealms uses 2 AI systems, each with different roles. ## The AI Package system determines what goals the NPC should attempt to achieve, and the GOAP AI system figures out how to achieve it. ## Override this to set custom behaviors by attaching to [NPCComponent]'s many signals. -var _npc:NPCComponent +@onready var _npc:NPCComponent = get_parent() ## Link this module to the component. @@ -15,8 +15,8 @@ func link(npc:NPCComponent) -> void: self._npc = npc -## Override this as a "_ready()" analogue. -func _initialize() -> void: +## The "ready" function if you depend on the NPC's variables. +func initialize() -> void: pass diff --git a/scripts/ai/goap_action.gd b/scripts/ai/goap_action.gd index c81c9ee..57a8454 100644 --- a/scripts/ai/goap_action.gd +++ b/scripts/ai/goap_action.gd @@ -1,68 +1,52 @@ class_name GOAPAction extends Node -## Base class for GOAP actions. - - -var prerequisites:Dictionary: - get: - return goap_behavior.prerequisites -var effects:Dictionary: - get: - return goap_behavior.effects -var cost:float: - get: - return goap_behavior.cost -var duration:float: - get: - return goap_behavior.duration -## Whether this objective is actively being worked on -var running:bool: - get: - return goap_behavior.running - set(val): - goap_behavior.running = val -var parent_goap:GOAPComponent: - get: - return goap_behavior.parent_goap -var entity:SKEntity: - get: - return goap_behavior.entity - -var goap_behavior:GOAPBehavior -func _init(behavior:GOAPBehavior = null) -> void: - if behavior: - goap_behavior = behavior - name = behavior.id +## The cost of this action when making a plan. +var cost:float = 1.0 +## Whether this objective is actively being worked on +var running:bool = false +var parent_goap:GOAPComponent +var entity:SKEntity +## The duration of this action. +var duration: float func is_achievable_given(state:Dictionary) -> bool: - return goap_behavior.is_achievable_given(state) + return state.has_all(get_prerequisites().keys()) func is_achievable() -> bool: - return goap_behavior.is_achievable() + return true func pre_perform() -> bool: - return goap_behavior.pre_perform() + return true func target_reached() -> bool: - return goap_behavior.target_reached() + return true func post_perform() -> bool: - return goap_behavior.post_perform() + return true func is_target_reached(agent:NavigationAgent3D) -> bool: - if not agent: - return true - return goap_behavior.is_target_reached(agent) + return agent.is_navigation_finished() func interrupt() -> void: - if goap_behavior: - goap_behavior.interrupt() + return + + +func get_prerequisites() -> Dictionary: + return {} + + +func get_effects() -> Dictionary: + return {} + + +func get_id() -> StringName: + return &"" diff --git a/scripts/ai/goap_behavior.gd b/scripts/ai/goap_behavior.gd deleted file mode 100644 index 130a73b..0000000 --- a/scripts/ai/goap_behavior.gd +++ /dev/null @@ -1,41 +0,0 @@ -class_name GOAPBehavior -extends Resource - - -@export var id:StringName -@export var prerequisites:Dictionary -@export var effects:Dictionary -@export var cost:float = 1 -@export var duration:float -## Whether this objective is actively being worked on -var running:bool = false -var parent_goap:GOAPComponent -var entity:SKEntity - - -func is_achievable_given(state:Dictionary) -> bool: - return state.has_all(prerequisites.keys()) - - -func is_achievable() -> bool: - return true - - -func pre_perform() -> bool: - return true - - -func target_reached() -> bool: - return true - - -func post_perform() -> bool: - return true - - -func is_target_reached(agent:NavigationAgent3D) -> bool: - return agent.is_navigation_finished() - - -func interrupt() -> void: - return diff --git a/scripts/ai/goap_group.gd b/scripts/ai/goap_group.gd deleted file mode 100644 index fcf2306..0000000 --- a/scripts/ai/goap_group.gd +++ /dev/null @@ -1,9 +0,0 @@ -class_name GOAPBehaviorGroup -extends Resource - - -## This is a reusable collection of GOAP Behaviors. The purpose is to allow a way to edit the GOAP Behaviors of large numbers of adents all at once. -## Please note this is meant for authoring. Changes made during runtime will not propogate as expected. - - -@export var behaviors:Array[GOAPBehavior] = [] diff --git a/scripts/ai/module_group.gd b/scripts/ai/module_group.gd deleted file mode 100644 index 21b2caa..0000000 --- a/scripts/ai/module_group.gd +++ /dev/null @@ -1,9 +0,0 @@ -class_name AIModuleGroup -extends Resource - - -## This is a reusable collection of AI Modules. The purpose is to allow a way to edit the AI modules of large numbers of NPCs all at once. -## Please note this is meant for authoring. Changes made during runtime will not propogate as expected. - - -@export var modules:Array[AIModule] = [] diff --git a/scripts/components/chest_component.gd b/scripts/components/chest_component.gd index f5fca31..a58d9a7 100644 --- a/scripts/components/chest_component.gd +++ b/scripts/components/chest_component.gd @@ -1,3 +1,4 @@ +@tool class_name ChestComponent extends SKEntityComponent @@ -5,22 +6,22 @@ extends SKEntityComponent ## Optionally refreshing inventories. -@export var loot_table:SKLootTable +@onready var loot_table:SKLootTable = get_child(0) @export_range(0, 100, 1, "or_greater") var reset_time_minutes:int ## How long it takes to refresh this chest, in in-game minutes. 0 will not refresh. +@export var owner_id:StringName var looted_time:Timestamp -var owner_id:StringName - - -func _init(oid:StringName = &"", lt:SKLootTable = null, rt:int = -1) -> void: - owner_id = oid - loot_table = lt - reset_time_minutes = rt func _ready() -> void: - reroll() + if Engine.is_editor_hint(): + return if reset_time_minutes > 0: GameInfo.minute_incremented.connect(_check_should_restore.bind()) + # If none provided, just generate a dummy loot table that will do nothing. + if loot_table == null: + var nlt := SKLootTable.new() + add_child(nlt) + loot_table = nlt func _check_should_restore() -> void: @@ -44,14 +45,19 @@ func reroll() -> void: var ic:InventoryComponent = parent_entity.get_component("InventoryComponent") var res: Dictionary = loot_table.resolve() - for id:ItemData in res.items: - var item: ItemInstance = ItemInstance.new() - item.item_data = id - item.contained_inventory = String(parent_entity.name) - item.item_owner = owner_id - item.ref_id = preload("res://addons/skelerealms/scripts/vendor/uuid.gd").v4() - - var e:SKEntity = SKEntityManager.instance.add_entity(item) + for id:PackedScene in res.items: + var e:SKEntity = SKEntityManager.instance.add_entity(id) ic.add_to_inventory(e.name) - + for id:StringName in res.entities: + ic.add_to_inventory(id) ic.currencies = res.currencies + + +func on_generate() -> void: + reroll() + + +func get_dependencies() -> Array[String]: + return [ + "InventoryComponent", + ] diff --git a/scripts/components/equipment_component.gd b/scripts/components/equipment_component.gd index 576933a..b61c066 100644 --- a/scripts/components/equipment_component.gd +++ b/scripts/components/equipment_component.gd @@ -26,7 +26,7 @@ func equip(item:StringName, slot:EquipmentSlots.Slots, silent:bool = false) -> b if not ic: return false # Get equippable data component - var ec = (ic as ItemComponent).data.get_component("EquippableDataComponent") + var ec = (ic as ItemComponent).get_component("EquippableDataComponent") if not ec: return false # Check slot validity diff --git a/scripts/components/goap_component.gd b/scripts/components/goap_component.gd index 748d081..f016fb7 100644 --- a/scripts/components/goap_component.gd +++ b/scripts/components/goap_component.gd @@ -24,20 +24,14 @@ func _init() -> void: add_child(_timer) -func setup(behavior_groups:Array[GOAPBehaviorGroup]) -> void: - var behaviors:Array[GOAPBehavior] = [] - - for g:GOAPBehaviorGroup in behavior_groups: - behaviors.append_array(g.behaviors) - - for b:GOAPBehavior in behaviors: - var new_behavior = b.duplicate(true) - new_behavior.entity = get_parent() - new_behavior.parent_goap = self - add_child(GOAPAction.new(new_behavior)) +func _ready() -> void: + for a:Node in get_children(): + if a is GOAPAction: + a.entity = parent_entity + a.parent_goap = self -func _process(delta): +func _process(delta:float) -> void: if GameInfo.is_loading: return # if we are set to rebuild our plan @@ -122,17 +116,17 @@ func _build_graph(parent:PlannerNode, leaves:Array[PlannerNode], goal:Dictionary # 1) achievable # 2) achievable given prerequisites # 3) has not already had effects satisfied <- may cause issues - var achievable_actions = action_pool.filter(func(x): return x.is_achievable() and x.is_achievable_given(parent.states) and not parent.states.has_all(x.effects.keys())) + var achievable_actions = action_pool.filter(func(x:GOAPAction): return x.is_achievable() and x.is_achievable_given(parent.states) and not parent.states.has_all(x.get_effects().keys())) achievable_actions.sort_custom(func(a,b): return a.cost < b.cost ) #Sort to resolve actions with least cost first. # due to the recursive nature of this function, we will be building branching paths from all of the actions until a valid path is found. - for action in achievable_actions: + for action:GOAPAction in achievable_actions: # if we can achieve this action, # duplicate our working set of states. var current_state:Dictionary = parent.states.duplicate(true) # Continue to accumulate effects in state, for passing on to the next node. - current_state.merge(action.effects, true) # overwrite to keep the state up to date. + current_state.merge(action.get_effects(), true) # overwrite to keep the state up to date. # create a new child planner node, which will have an accumulation of all the previous costs. # this will help us find the shortest path later. @@ -162,7 +156,7 @@ func _goal_achieved(goal:Dictionary, current_state:Dictionary) -> bool: ## Invoke a callable in a set amount of time. -func _invoke_in_time(f:Callable, time:float): +func _invoke_in_time(f:Callable, time:float) -> void: # Invoke immediately if no duration if time == 0: f.call() @@ -184,7 +178,7 @@ func _clear_timer() -> void: ## Wrap up the running action. -func _complete_current_action(): +func _complete_current_action() -> void: _current_action.running = false # if post perform fails, rebuild plan if not _current_action.post_perform(): diff --git a/scripts/components/inventory_component.gd b/scripts/components/inventory_component.gd index f0c6fa1..1e79b58 100644 --- a/scripts/components/inventory_component.gd +++ b/scripts/components/inventory_component.gd @@ -1,9 +1,13 @@ class_name InventoryComponent extends SKEntityComponent -## Keeps track of an inventory. -## The RefIDs of the items in the inventory. -var inventory: PackedStringArray + +## Keeps track of an inventory and currencies. +## If you add an [SKLootTable] node underneath, the loot table will be rolled upon generating. See [method SKEntityComponent.on_generate]. + + +## The RefIDs of the items in the inventory. Put any unique items in here. +@export var inventory: PackedStringArray ## The amount of cash moneys. var currencies = {} @@ -92,8 +96,25 @@ func get_items_that(fn: Callable) -> Array[StringName]: return pt -func get_items_of_base(id:String) -> Array[StringName]: - return get_items_that(func(x:StringName): return ItemComponent.get_item_component(x).data.id == id) +func get_items_of_form(id:String) -> Array[StringName]: + return get_items_that(func(x:StringName): return ItemComponent.get_item_component(x).parent_entity.form_id == id) + + +func on_generate() -> void: + if get_child_count() == 0: + return + var lt:SKLootTable = get_child(0) as SKLootTable + if not lt: + return + + var res: Dictionary = lt.resolve() + + for id:PackedScene in res.items: + var e:SKEntity = SKEntityManager.instance.add_entity(id) + add_to_inventory(e.name) + for id:StringName in res.entities: + ic.add_to_inventory(id) + currencies = res.currencies func gather_debug_info() -> String: diff --git a/scripts/components/item_component.gd b/scripts/components/item_component.gd index 006696a..1e6c5c7 100644 --- a/scripts/components/item_component.gd +++ b/scripts/components/item_component.gd @@ -1,3 +1,4 @@ +@tool class_name ItemComponent extends SKEntityComponent ## Keeps track of item data @@ -6,10 +7,9 @@ extends SKEntityComponent const DROP_DISTANCE:float = 2 const NONE:StringName = &"" -## The data blob this item has. -@export var data: ItemData + ## What inventory this item is in. -var contained_inventory: StringName = NONE: +@export var contained_inventory: StringName = NONE: get: return contained_inventory set(val): @@ -17,13 +17,13 @@ var contained_inventory: StringName = NONE: if parent_entity: parent_entity.supress_spawning = not contained_inventory == NONE # prevent spawning if item is in inventory ## Whether this item is in inventory or not. -var in_inventory:bool: +@export var in_inventory:bool: get: return not contained_inventory == NONE ## If this is a quest item. -var quest_item:bool +@export var quest_item:bool ## If this item is "owned" by someone. -var item_owner:StringName = NONE: +@export var item_owner:StringName = NONE: get: return item_owner set(val): @@ -31,12 +31,14 @@ var item_owner:StringName = NONE: if get_parent() == null: #stops this from being called while setting up return if val == &"": - $"../InteractiveComponent".interact_verb = "TAKE" + inv.interact_verb = "TAKE" else: # TODO: Determine using worth and owner relationships - $"../InteractiveComponent".interact_verb = "STEAL" + inv.interact_verb = "STEAL" var stolen:bool ## If this has been stolen or not. var durability:float ## This item's durability, if your game has condition/durability mechanics like Fallout or Morrowind. +var psc:PuppetSpawnerComponent +var inv:InteractiveComponent ## Shorthand to get an item component for an entity by ID. @@ -56,39 +58,28 @@ func _init() -> void: func _ready() -> void: + if Engine.is_editor_hint(): + return super._ready() if parent_entity: parent_entity.supress_spawning = in_inventory + psc = parent_entity.get_component("PuppetSpawnerComponent") + inv = parent_entity.get_component("InteractiveComponent") func _entity_ready() -> void: - $"../InteractiveComponent".interacted.connect(interact.bind()) - $"../InteractiveComponent".translation_callback = get_translated_name.bind() + inv.interacted.connect(interact.bind()) + inv.translation_callback = get_translated_name.bind() if item_owner == &"": - $"../InteractiveComponent".interact_verb = "TAKE" + inv.interact_verb = "TAKE" else: # TODO: Determine using worth and owner relationships - $"../InteractiveComponent".interact_verb = "STEAL" - - -func _on_enter_scene(): - _spawn() - - -func _spawn(): - $"../PuppetSpawnerComponent".spawn(data.prefab) - ($"../PuppetSpawnerComponent".get_child(0) as ItemPuppet).quaternion = parent_entity.rotation # TODO: This doesn't work. - - -func _on_exit_scene(): - _despawn() - - -func _despawn(): - $"../PuppetSpawnerComponent".despawn() + inv.interact_verb = "STEAL" func _process(delta): + if Engine.is_editor_hint(): + return if in_inventory: parent_entity.position = SKEntityManager.instance.get_entity(contained_inventory).position parent_entity.world = SKEntityManager.instance.get_entity(contained_inventory).world @@ -117,13 +108,13 @@ func move_to_inventory(refID:StringName): contained_inventory = refID if in_inventory: - _despawn() + psc.despawn() ## Drop this on the ground. func drop(): var e:SKEntity = SKEntityManager.instance.get_entity(contained_inventory) - var drop_dir:Quaternion = e.rotation + var drop_dir:Quaternion = e.quaternion print(drop_dir.get_euler().normalized() * DROP_DISTANCE) # This whole bit is genericizing dropping the item in front of the player. It's meant to be used with the player, it should work with anything with a puppet. if in_inventory: @@ -132,7 +123,6 @@ func drop(): .remove_from_inventory(parent_entity.name) # raycast in front of puppet if possible to do wall check - var psc = e.get_component("PuppetSpawnerComponent") if e.in_scene and psc: print("has puppet component, in scene") if psc.puppet: @@ -150,20 +140,20 @@ func drop(): print("didn't hit anything") parent_entity.position = to contained_inventory = NONE - _spawn() # Should check if we are in scene, although nothing should drop in the Ether + psc.spawn() return else: # if hit something, spawn at hit position print(res) parent_entity.position = res["position"] # TODO: Compensate for item size contained_inventory = NONE - _spawn() # Should check if we are in scene, although nothing should drop in the Ether + psc.spawn() return parent_entity.position = parent_entity.position + Vector3(0, 1.5, 0) contained_inventory = NONE - _spawn() # Should check if we are in scene, although nothing should drop in the Ether + psc.spawn() ## Interact with this item. Called from [InteractiveComponent]. @@ -185,6 +175,20 @@ func allow() -> void: item_owner = &""; +## Whether it has a component type. [code]c[/code] is the name of the component type, like "HoldableDataComponent". +func has_component(c:String) -> bool: + return get_children().any(func(x:ItemDataComponent): return x.get_type() == c) + + +## Gets the first component of a type. [code]c[/code] is the name of the component type, like "HoldableDataComponent". +func get_component(c:String) -> ItemDataComponent: + var valid_components = get_children().filter(func(x:ItemDataComponent): return x.get_type() == c) + if valid_components.is_empty(): + return null + else: + return valid_components[0] + + func save() -> Dictionary: return { "contained_inventory" = contained_inventory, @@ -200,7 +204,7 @@ func load_data(data:Dictionary): func get_translated_name() -> String: var t = tr(parent_entity.name) if t == parent_entity.name: - return tr(data.id) + return tr(parent_entity.form_id) else : return t @@ -216,3 +220,10 @@ func gather_debug_info() -> String: item_owner, quest_item ] + + +func get_dependencies() -> Array[String]: + return [ + "PuppetSpawnerComponent", + "InteractiveComponent" + ] diff --git a/scripts/components/marker_component.gd b/scripts/components/marker_component.gd index fe2423e..d487955 100644 --- a/scripts/components/marker_component.gd +++ b/scripts/components/marker_component.gd @@ -1,3 +1,4 @@ +@tool class_name MarkerComponent extends SKEntityComponent ## Component tag for [WorldMarker]s. @@ -6,11 +7,25 @@ extends SKEntityComponent var rotation:Quaternion -func _init(rot:Quaternion) -> void: +func _init(rot:Quaternion = Quaternion.IDENTITY) -> void: name = "MarkerComponent" rotation = rot func _ready() -> void: + if Engine.is_editor_hint(): + return super._ready() parent_entity.rotation = rotation + + +func get_world_entity_preview() -> Node: + var sphere := MeshInstance3D.new() + sphere.mesh = SphereMesh.new() + + var mat := StandardMaterial3D.new() + mat.albedo_color = Color.BLUE + mat.albedo_color.a = 0.5 + + sphere.set_surface_override_material(0, mat) + return sphere diff --git a/scripts/components/navigator_component.gd b/scripts/components/navigator_component.gd index bf2c0b7..b9b3484 100644 --- a/scripts/components/navigator_component.gd +++ b/scripts/components/navigator_component.gd @@ -6,8 +6,8 @@ extends SKEntityComponent ## Calculate a path from the entity's current position to a [NavPoint]. ## Array is empty if no path is found. func calculate_path_to(pt:NavPoint) -> Array[NavPoint]: - # TODO: This - return [] + var start := NavPoint.new(parent_entity.world, parent_entity.position) + return NavMaster.instance.calculate_path(start, pt) func _init() -> void: diff --git a/scripts/components/npc_component.gd b/scripts/components/npc_component.gd index 6b21884..9a44033 100644 --- a/scripts/components/npc_component.gd +++ b/scripts/components/npc_component.gd @@ -1,3 +1,4 @@ +@tool class_name NPCComponent extends SKEntityComponent ## The brain for an NPC. Handles AI behavior, scheduling, combat, dialogue interactions. @@ -7,14 +8,34 @@ extends SKEntityComponent ## @tutorial(In-depth view of opinion system): https://github.com/SlashScreen/skelerealms/wiki/NPCs#opinions-and-how-the-npc-determines-its-opinions -const THREATENING_ENTITY_TYPES = [ +@export_category("Flags") +## Whether this NPC is essential to the story, and them dying would screw things up. +@export var essential:bool = true +## Whether this NPC is a ghost. +@export var ghost:bool +## Whether this NPC can't take damage. +@export var invulnerable:bool +## Whether this NPC is unique. +@export var unique:bool = true +## Whether this NPC affects the stealth meter when it sees you. +@export var affects_stealth_meter:bool = true +## Whether you can interact with this NPC. +@export var interactive:bool = true +@export_category("AI") +## NPC relationships. +@export var relationships:Array[Relationship] +## Component types that the AI will looks for to determine threats. +@export var threatening_enemy_types = [ "NPCComponent", "PlayerComponent", ] +## Opinions of entities. StringName:float +@export var npc_opinions = {} +## Loyalty of this NPC. Determines weights of opinion calculations. +@export_enum("None", "Covens", "Self") var loyalty:int = 0 +## How the opinion of something is calculated. +@export_enum("Minimum", "Maximum", "Average") var opinion_mode:int = 0 -#* Export -## Base data for this NPC. -@export var data: NPCData #* Public var player_opinion:int var visibility_threshold:float = 0.3 @@ -80,8 +101,7 @@ var _perception_memory:Dictionary = {} var _combat_target:String ## Navigation path. var _path:Array[NavPoint] -## Opinions of entities -var _opinions = {} + ## Signal emitted when this NPC enters combat. @@ -145,46 +165,31 @@ static func get_npc_component(id:StringName) -> NPCComponent: #* ### OVERRIDES -func _init(d:NPCData) -> void: - data = d +func _init() -> void: name = "NPCComponent" - var s:Schedule = Schedule.new() - s.name = "Schedule" - s.events = data.schedule - add_child(s) - _schedule = s - - # Set default player opinion - _opinions[&"Player"] = d.default_player_opinion func _ready(): + if Engine.is_editor_hint(): + return + super._ready() # Initialize all AI Modules var modules:Array[AIModule] = [] - for group: AIModuleGroup in data.ai_modules: - modules.append_array(group.modules) - for module:AIModule in modules: - var n:AIModule = module.duplicate() - n.link(self) - n._initialize() - ai_modules.append(n) - # FIXME: Parent entity can be instantiated called BEFORE this. - - -func _entity_ready() -> void: - _nav_component = $"../NavigatorComponent" as NavigatorComponent + for module:Node in get_children(): + if not module is AIModule: + continue + modules.append(module) + + _nav_component = parent_entity.get_component("NavigatorComponent") as NavigatorComponent # Puppet manager component. - _puppet_component = $"../PuppetSpawnerComponent" as PuppetSpawnerComponent + _puppet_component = parent_entity.get_component("PuppetSpawnerComponent") as PuppetSpawnerComponent # Interactive component. - _interactive_component = $"../InteractiveComponent" as InteractiveComponent + _interactive_component = parent_entity.get_component("InteractiveComponent") as InteractiveComponent # Behavior planner. - _goap_component = $"../GOAPComponent" as GOAPComponent - ($"../InteractiveComponent" as InteractiveComponent).interacted.connect(func(x:String): interacted.emit(x)) - - # goap setup - _goap_component.setup(data.goap_behaviors) + _goap_component = parent_entity.get_component("GOAPComponent") as GOAPComponent + _interactive_component.interacted.connect(func(x:String): interacted.emit(x)) # sync nav agent _puppet_component.spawned_puppet.connect(func(x:Node): @@ -195,25 +200,34 @@ func _entity_ready() -> void: _puppet = null _goap_component._agent = null ) - + # schedule + var s := get_node_or_null("Schedule") + if s: + _schedule = s + else: + var n := Schedule.new() + n.name = "Schedule" + add_child(n) + _schedule = n # misc setup - _interactive_component.interactible = data.interactive # TODO: Or instance override _interactive_component.translation_callback = get_translated_name.bind() GameInfo.minute_incremented.connect(_calculate_new_schedule.bind()) + for a:AIModule in modules: + a.initialize() func _on_enter_scene(): - _puppet_component.spawn(data.prefab) _sim_level = SimulationLevel.FULL func _on_exit_scene(): - _puppet_component.despawn() _sim_level = SimulationLevel.GRANULAR func _process(delta): + if Engine.is_editor_hint(): + return #* Section 1: Path following # If in scene, use navmesh agent. if _current_target_point: @@ -229,6 +243,15 @@ func _process(delta): updated.emit(delta) +func get_dependencies() -> Array[String]: + return [ + "InteractiveComponent", + "PuppetSpawnerComponent", + "NavigatorComponent", + "GOAPComponent", + ] + + func _exit_tree() -> void: for m in ai_modules: m._clean_up() @@ -453,7 +476,7 @@ func _calculate_new_schedule() -> void: ## Get a relationship this NPC has of [RelationshipType]. Pass in the type's key. Returns the relationship if found, none if none found. func get_relationship_of_type(key:String) -> Option: - var res = data.relationships.filter(func(r:Relationship): return r.relationship_type and r.relationship_type.relationship_key == key) + var res = relationships.filter(func(r:Relationship): return r.relationship_type and r.relationship_type.relationship_key == key) if res.is_empty(): return Option.none() return Option.from(res[0]) @@ -461,7 +484,7 @@ func get_relationship_of_type(key:String) -> Option: ## Gets this NPC's relationship with someone by ref id. Returns the relationship if found, none if none found. func get_relationship_with(ref_id:String) -> Option: - var res = data.relationships.filter(func(r:Relationship): return r.relationship_type and r.other_person == ref_id) + var res = relationships.filter(func(r:Relationship): return r.relationship_type and r.other_person == ref_id) if res.is_empty(): return Option.none() return Option.from(res[0]) @@ -471,7 +494,7 @@ func get_relationship_with(ref_id:String) -> Option: func determine_opinion_of(id:StringName) -> float: var e:SKEntity = SKEntityManager.instance.get_entity(id) - if not THREATENING_ENTITY_TYPES.any(func(x:String): return not e.get_component(x) == null): # if it doesn't have any components that are marked as threatening, return neutral. + if not threatening_enemy_types.any(func(x:String): return not e.get_component(x) == null): # if it doesn't have any components that are marked as threatening, return neutral. return 0 var e_cc = e.get_component("CovensComponent") @@ -479,32 +502,41 @@ func determine_opinion_of(id:StringName) -> float: var opinion_total = 0 # calculate modifiers - var covens_modifier = 2 if data.loyalty == 1 else 1 # if values covens, increase modifier - var self_modifier = 2 if data.loyalty == 2 else 1 # ditto + var covens_modifier = 2 if loyalty == 1 else 1 # if values covens, increase modifier + var self_modifier = 2 if loyalty == 2 else 1 # ditto # if has other covens, compare against ours if e_cc: var covens = parent_entity.get_component("CovensComponent").covens - var coven_opinions_unfiltered = [] + var covennpc_opinions_unfiltered = [] var e_covens_component = e_cc # get all opinions for coven in covens: var c = CovenSystem.get_coven(coven) # get the other coven opinions - coven_opinions_unfiltered.append_array(c.get_coven_opinions(e_covens_component.covens.keys())) # FIXME: Get this coven opinions on other + covennpc_opinions_unfiltered.append_array(c.get_covennpc_opinions(e_covens_component.covens.keys())) # FIXME: Get this coven opinions on other # take crimes into account opinions.append(CrimeMaster.max_crime_severity(id, coven) * -10) # sing opinion by -10 for each severity point - opinions.append_array(coven_opinions_unfiltered.filter(func(x:int): return not x == 0)) # filter out zeroes + opinions.append_array(covennpc_opinions_unfiltered.filter(func(x:int): return not x == 0)) # filter out zeroes opinion_total += opinions.size() * covens_modifier # calculate total # if has an opinion of the player, take into account - if _opinions.has(id) and not _opinions[id] == 0: - opinions.append(_opinions[id]) + if npc_opinions.has(id) and not npc_opinions[id] == 0: + opinions.append(npc_opinions[id]) opinion_total += self_modifier # avoid 1 * self_modifier because that's an identity function so we can just do self_modifier - # Return weighted average - return opinions.reduce(func(sum, next): return sum + next, 0) / (1 if opinion_total == 0 else opinion_total) + match opinion_mode: + 0: + var o:Variant = opinions.min() + return 0.0 if o == null else o + 1: + var o:Variant = opinions.max() + return 0.0 if o == null else o + 2: + return opinions.reduce(func(sum, next): return sum + next, 0) / (1 if opinion_total == 0 else opinion_total) + _: + return 0.0 func gather_debug_info() -> String: @@ -531,7 +563,10 @@ func gather_debug_info() -> String: func get_translated_name() -> String: var t = tr(parent_entity.name) if t == parent_entity.name: - return tr(data.id) + if parent_entity.form_id.is_empty(): + return parent_entity.name + else: + return tr(parent_entity.form_id) else: return t diff --git a/scripts/components/player_component.gd b/scripts/components/player_component.gd index c43aa9e..18ee101 100644 --- a/scripts/components/player_component.gd +++ b/scripts/components/player_component.gd @@ -32,11 +32,12 @@ func set_entity_position(pos:Vector3): func set_entity_rotation(q:Quaternion) -> void: - parent_entity.rotation = q + parent_entity.quaternion = q func _process(delta): - parent_entity.world = GameInfo.world + if not parent_entity.world == GameInfo.world: + parent_entity.world = GameInfo.world if _set_up: return diff --git a/scripts/components/puppet_spawner_component.gd b/scripts/components/puppet_spawner_component.gd index 3981d3a..24e4c6a 100644 --- a/scripts/components/puppet_spawner_component.gd +++ b/scripts/components/puppet_spawner_component.gd @@ -1,7 +1,10 @@ +@tool class_name PuppetSpawnerComponent extends SKEntityComponent ## Manages spawning and despawning of puppets. + +var prefab: PackedScene ## The puppet node. var puppet:Node @@ -14,17 +17,41 @@ func _init() -> void: func _ready(): + if Engine.is_editor_hint(): + return super._ready() # brute force getting the puppet for the player if it already exists. if get_child_count() > 0: puppet = get_child(0) +func get_world_entity_preview() -> Node: + return get_child(0) + + +func _on_enter_scene() -> void: + spawn() + + +func _on_exit_scene() -> void: + despawn() + + ## Spawn a new puppet. -func spawn(data:PackedScene): - var n = data.instantiate() - add_child(n) - (n as Node3D).set_position(parent_entity.position) +func spawn(): + var n:Node3D + if not prefab and get_child_count() > 0: + var ps: PackedScene = PackedScene.new() + ps.pack(get_child(0)) + prefab = ps + n = get_child(0) + else: + if not prefab: + printe("Failed spawning: no prefab.") + return + n = prefab.instantiate() + add_child(n) + n.set_position(parent_entity.position) puppet = n spawned_puppet.emit(puppet) printe("spawned at %s : %s" % [parent_entity.world, parent_entity.position]) @@ -33,6 +60,11 @@ func spawn(data:PackedScene): ## Despawn a puppet. func despawn(): printe("despawned.") + if not prefab: + var ps: PackedScene = PackedScene.new() + ps.pack(get_child(0)) + prefab = ps + for n in get_children(): n.queue_free() puppet = null diff --git a/scripts/components/shop_component.gd b/scripts/components/shop_component.gd new file mode 100644 index 0000000..9d7c670 --- /dev/null +++ b/scripts/components/shop_component.gd @@ -0,0 +1,12 @@ +class_name ShopComponent +extends ChestComponent + + +## Base value of how much (from 0-1) of the total price that the merchant will tollerate haggling. +@export var haggle_tolerance:float +## Only items with at least one of these tags can be sold to this vendor. +@export var whitelist:Array[StringName] = [] +## No items with at least one of these tags can be sold to this vendor. Supercedes [member whitelist]. +@export var blacklist:Array[StringName] = [] +## Whether this merchant accepts stolen goods. +@export var accept_stolen:bool diff --git a/scripts/data/ItemDataComponents/equippable_component.gd b/scripts/data/ItemDataComponents/equippable_data_component.gd similarity index 100% rename from scripts/data/ItemDataComponents/equippable_component.gd rename to scripts/data/ItemDataComponents/equippable_data_component.gd diff --git a/scripts/data/ItemDataComponents/item_data_component.gd b/scripts/data/ItemDataComponents/item_data_component.gd index a14df0e..4446773 100644 --- a/scripts/data/ItemDataComponents/item_data_component.gd +++ b/scripts/data/ItemDataComponents/item_data_component.gd @@ -1,5 +1,5 @@ class_name ItemDataComponent -extends Resource +extends Node ## Base class for item data components that describe the capabilities of an item. See [ItemData]. [br] ## Items are a special case, in that they are built up of components. ## This may seem a bit weird and convoluted, and while it is, this allows for a much more extensible and flexible system. diff --git a/scripts/data/item_data.gd b/scripts/data/item_data.gd index 6208b52..efbb979 100644 --- a/scripts/data/item_data.gd +++ b/scripts/data/item_data.gd @@ -22,19 +22,3 @@ extends RefData @export var stackable:bool ## Tags this item has. @export var tags:Array[StringName] = [] -## The components that describe this item. -@export var components:Array[ItemDataComponent] - - -## Whether it has a component type. [code]c[/code] is the name of the component type, like "HoldableDataComponent". -func has_component(c:String) -> bool: - return components.any(func(x:ItemDataComponent): return x.get_type() == c) - - -## Gets the first component of a type. [code]c[/code] is the name of the component type, like "HoldableDataComponent". -func get_component(c:String) -> ItemDataComponent: - var valid_components = components.filter(func(x:ItemDataComponent): return x.get_type() == c) - if valid_components.is_empty(): - return null - else: - return valid_components[0] diff --git a/scripts/data/npc_data.gd b/scripts/data/npc_data.gd index 9de56b2..8d10774 100644 --- a/scripts/data/npc_data.gd +++ b/scripts/data/npc_data.gd @@ -13,7 +13,9 @@ class_name NPCData ## What the default opinion of the player is. @export var default_player_opinion:int = 0 ## Loyalty of this NPC. Determines weights of opinion calculations. -@export_enum("None", "Covens", "Self") var loyalty = 0 +@export_enum("None", "Covens", "Self") var loyalty:int = 0 +## How the opinion of something is calculated. +@export_enum("Minimum", "Maximum", "Average") var opinion_mode:int = 0 @export_category("Flags") ## Whether this NPC is essential to the story, and them dying would screw things up. @@ -30,11 +32,3 @@ class_name NPCData @export var interactive:bool = true ## NPC relationships. @export var relationships:Array[Relationship] - -@export_category("AI") -## AI Modules. -@export var ai_modules:Array[AIModuleGroup] -## GOAP Actions. -@export var goap_behaviors:Array[GOAPBehaviorGroup] -## The loot table of this kind of NPC. -@export var loot_table:SKLootTable diff --git a/scripts/entities/entity.gd b/scripts/entities/entity.gd index 413ef0c..e855fca 100644 --- a/scripts/entities/entity.gd +++ b/scripts/entities/entity.gd @@ -1,23 +1,24 @@ +@tool class_name SKEntity extends Node ## An entity for the pseudo-ecs. Contains [SKEntityComponent]s. ## These allow constructs such as NPCs and Items to persist even when not in the scene. -## The world this entity is in. -@export var world: String -## Position within the world it's in. -@export var position:Vector3 -## Rotation of this Enitiy. -@export var rotation:Quaternion = Quaternion.IDENTITY +@export var form_id: StringName ## This is what [i]kind[/i] of entity it is. For example, Item "awesome_sword" has a form ID of "iron_sword". +@export var world: String: ## The world this entity is in. + set(val): + world = val + printe("Setting world to %s" % val) +@export var position:Vector3 ## The entity's position in the world it lives within. +@export var rotation: Quaternion = Quaternion.IDENTITY ## The entity's rotation. +@export var unique:bool = true ## Whether this is the only entity of this setup. Usually used for named NPCs and the like. ## An internal timer of how long this entity has gone without being modified or referenced. ## One it's beyond a certain point, the [SKEntityManager] will mark it for cleanup after a save. var stale_timer:float ## This is used to prevent items from spawning, even if they are supposed to be in scene. ## For example, items in invcentories should not spawn despite technically being "in the scene". var supress_spawning:bool -## FLag telling if this was created dynamically (eg. [class SpawnPoint]). -var generated:bool ## Whether this entity is in the scene or not. var in_scene: bool: get: @@ -39,25 +40,7 @@ signal entered_scene signal instantiated -func _init(res:InstanceData = null) -> void: - if not res: - return - - var new_nodes = res.get_archetype_components() # Get the entity components - for c in get_children(): # if this entity already exists, get rid of the components. FIXME: This is inefficient. - c.queue_free() - - name = res.ref_id # set its name to the instance refID - world = res.world - position = res.position - if not res.get("rotation") == null: - rotation = res.rotation - - for n in new_nodes: # add all components to entity - add_child(n) - n.owner = self - (n as SKEntityComponent).parent_entity = self - +func _init() -> void: # call entity ready instantiated.emit() for c in get_children(): @@ -65,11 +48,23 @@ func _init(res:InstanceData = null) -> void: func _ready(): + if Engine.is_editor_hint(): + return add_to_group("savegame_entity") +func _enter_tree() -> void: + if Engine.is_editor_hint(): + print(scene_file_path) + return + if not get_parent() is SKEntityManager: + queue_free() + + # Called every frame. 'delta' is the elapsed time since the previous frame. func _process(delta): + if Engine.is_editor_hint(): + return _should_be_in_scene() # If we aren't in the scene, start counting up. Otherwise, we are still in the scene with the player and shouldn't depsawn. if not in_scene: @@ -126,7 +121,7 @@ func save() -> Dictionary: # TODO: Determine if instance is saved to disk. If no "entity_data": { "world" = world, "position" = position, - "generated" = generated + "unique" = unique } } for c in get_children().filter(func(x:SKEntityComponent): return x.dirty): # filter to get dirty acomponents @@ -137,7 +132,7 @@ func save() -> Dictionary: # TODO: Determine if instance is saved to disk. If no func load_data(data:Dictionary) -> void: world = data["entity_data"]["world"] position = JSON.parse_string(data["entity_data"]["position"]) - generated = JSON.parse_string(data["entity_data"]["generated"]) + unique = JSON.parse_string(data["entity_data"]["unique"]) # loop through all saved components and call load for d in data["components"]: @@ -149,7 +144,7 @@ func reset_data() -> void: # TODO: Figure out how to reset entities that are generated at runtime. oh boy that's gonna be fun. var i = SKEntityManager.instance.get_disk_data_for_entity(name) if i: - _init(i) + _init() func reset_stale_timer() -> void: @@ -167,17 +162,33 @@ func dialogue_command(command:String, args:Array) -> void: c._try_dialogue_command(command, args) -func gather_debug_info() -> Array[String]: - var info: Array[String] = [] +## Get a preview scene tree from this entity, if applicable. This is used for getting previews for [class SKWorldEntity]. +func get_world_entity_preview() -> Node: + for c:Node in get_children(): + if c.has_method(&"get_world_entity_preview"): + return c.get_world_entity_preview() + return null + + +## Call this when an entity is generated for the first time; eg. a non-unique Spider enemy is spawned. +func generate() -> void: + for c:Node in get_children(): + c.on_generate() + + +func gather_debug_info() -> PackedStringArray: + var info := PackedStringArray() info.push_back(""" [b]SKEntity[/b] - SKEntity RefID: %s + RefID: %s + FormID: %s World: %s Position: x%s y%s z%s - Rotation: x%s y%s z%s w%s + Rotation: x%s y%s z%s In scene: %s """ % [ name, + form_id, world, position.x, position.y, @@ -185,7 +196,6 @@ func gather_debug_info() -> Array[String]: rotation.x, rotation.y, rotation.z, - rotation.w, in_scene ]) @@ -197,6 +207,18 @@ func gather_debug_info() -> Array[String]: return info +func _to_string() -> String: + return "\n".join(gather_debug_info()) + + ## Prints a rich text message to the console prepended with the entity name. Used for easier debugging. func printe(text:String) -> void: - print_rich("[b]%s[/b]: %s" % [name, text]) + print_rich("[b]%s[/b]: %s\n%s" % [name, text, _format_stack_trace()]) + + +func _format_stack_trace() -> String: + var trace:Array = get_stack() + var output := "[indent]" + for d:Dictionary in trace: + output += "%s: %s line %d\n" % [d.function, d.source, d.line] + return output diff --git a/scripts/entities/entity_component.gd b/scripts/entities/entity_component.gd index 9b04930..956549a 100644 --- a/scripts/entities/entity_component.gd +++ b/scripts/entities/entity_component.gd @@ -12,6 +12,9 @@ var dirty:bool = false # Called when the node enters the scene tree for the first time. func _ready(): + if Engine.is_editor_hint(): + return + parent_entity = get_parent() as SKEntity if not parent_entity.left_scene.is_connected(_on_exit_scene.bind()): parent_entity.left_scene.connect(_on_exit_scene.bind()) @@ -33,22 +36,56 @@ func _on_exit_scene(): pass +## Process a dialogue command given to the entity. func _try_dialogue_command(command:String, args:Array) -> void: pass +## Gather data to save. func save() -> Dictionary: return {} +## Load a data blob from the savegame system. func load_data(data:Dictionary): pass +## Gather and format any relevant info for a debug console or some other debugger. func gather_debug_info() -> String: return "" +func _to_string() -> String: + return gather_febug_info() + + ## Prints a rich text message to the console prepended with the entity name. Used for easier debugging. func printe(text:String) -> void: - parent_entity.printe(text) + if parent_entity: + parent_entity.printe(text) + else: + (get_parent() as SKEntity).printe(text) + + +## Get the dependencies for this node, for error warnings. Dependencies are the class name as a string. +func get_dependencies() -> Array[String]: + return [] + + +## Do any first-time setup needed for this component. For example, roll a loot table, randomize facial attributes, etc. +func on_generate() -> void: + pass + + +func _get_configuration_warnings() -> PackedStringArray: + var output := PackedStringArray() + + if not (get_parent() is SKEntity or get_parent() is SKElementGroup): + output.push_back("Component should be the child of an SKEntity or an SKElementGroup.") + + for dep:String in get_dependencies(): + if not get_parent().has_node(dep): + output.push_back("This component needs %s" % dep) + + return output diff --git a/scripts/entities/entity_manager.gd b/scripts/entities/entity_manager.gd index 47cdea4..178f732 100644 --- a/scripts/entities/entity_manager.gd +++ b/scripts/entities/entity_manager.gd @@ -17,7 +17,7 @@ func _init() -> void: func _ready(): regex = RegEx.new() - regex.compile("([^\\/\n\\r]+)\\.t?res") + regex.compile("([^\\/\n\\r]+)\\.t?scn") _cache_entities(ProjectSettings.get_setting("skelerealms/entities_path")) SkeleRealmsGlobal.entity_manager_loaded.emit() config.compile() @@ -39,15 +39,16 @@ func get_entity(id: StringName) -> SKEntity: # stage 2: Check in save file var potential_data = SaveSystem.entity_in_save(id) # chedk the save system if potential_data.some(): # if found: - add_entity(ResourceLoader.load(disk_assets[id], "InstanceData")) # load default from disk - entities[id].load_data(potential_data.unwrap()) # and then load using the data blob we got from the save file - (entities[id] as SKEntity).reset_stale_timer() - return entities[id] + var e:SKEntity = add_entity_from_scene(ResourceLoader.load(disk_assets[id])) # load default from disk + e.load_data(potential_data.unwrap()) # and then load using the data blob we got from the save file + e.reset_stale_timer() + return e # stage 3: check on disk if disk_assets.has(id): - add_entity(load(disk_assets[id])) - (entities[id] as SKEntity).reset_stale_timer() - return entities[id] # we added the entity in #add_entity + var e:SKEntity = add_entity_from_scene(ResourceLoader.load(disk_assets[id])) + e.generate() # generate, because the entity has never been seen before + e.reset_stale_timer() + return e # Other than that, we've failed. Attempt to find the entity in the child count as a failsave, then return none. return get_node_or_null(id as String) @@ -81,13 +82,13 @@ func _cache_entities(path: String): print("An error occurred when trying to access the path.") -## add a new entity. -func add_entity(res: InstanceData) -> SKEntity: - var new_entity = SKEntity.new(res) # make a new entity +# add a new entity. +#func add_entity(res: InstanceData) -> SKEntity: + #var new_entity = SKEntity.new(res) # make a new entity # add new entity to self, and the dictionary - entities[res.ref_id] = new_entity - add_child(new_entity) - return new_entity + #entities[res.ref_id] = new_entity + #add_child(new_entity) + #return new_entity func _add_entity_raw(e: SKEntity) -> SKEntity: @@ -112,3 +113,19 @@ func remove_entity(rid: StringName) -> void: if entities.has(rid): entities[rid].queue_free() entities.erase(rid) + + +func add_entity_from_scene(scene:PackedScene) -> SKEntity: + var e:SKEntity = scene.instantiate() + if not e: + push_error("Scene at path %s isn't a valid entity." % scene.resource_path) + + if not e.unique: + var valid: bool = false + var new_id: String = "" + while not valid: + new_id = SKIDGenerator.generate_id() + valid = not entities.has(new_id) + e.generate.call_deferred() + e.name = new_id + return _add_entity_raw(e) diff --git a/scripts/fsm/fsm_machine.gd b/scripts/fsm/fsm_machine.gd index 0c40dd6..4d2f583 100644 --- a/scripts/fsm/fsm_machine.gd +++ b/scripts/fsm/fsm_machine.gd @@ -34,7 +34,7 @@ func _process(delta: float) -> void: ## Transition to a new state by state name. DOes nothing if no state with name found. func transition(state_name:String, msg:Dictionary = {}) -> void: - print("transitioning from %s state to %s" % [state.name if state else "None", state_name]) + #print("transitioning from %s state to %s" % [state.name if state else "None", state_name]) if not has_node(state_name): return diff --git a/scripts/granular_navigation/nav_point.gd b/scripts/granular_navigation/nav_point.gd index 7bbea86..b39dfd0 100644 --- a/scripts/granular_navigation/nav_point.gd +++ b/scripts/granular_navigation/nav_point.gd @@ -1,4 +1,5 @@ class_name NavPoint +extends RefCounted ## Point in a world. diff --git a/scripts/granular_navigation/navigation_master.gd b/scripts/granular_navigation/navigation_master.gd index 3dae1d2..3e5eb6e 100644 --- a/scripts/granular_navigation/navigation_master.gd +++ b/scripts/granular_navigation/navigation_master.gd @@ -11,12 +11,14 @@ extends Node ## See project setting [code]skelerelams/granular_navigation_sim_distance[/code] to adjust how far away the actors have to be before they stop using this system and just stay idle. +static var instance:NavMaster ## Dictionary of references to the roots of KD trees. var worlds:Dictionary = {} func _ready() -> void: GameInfo.game_started.connect(load_all_networks.bind()) + instance = self func calculate_path(start:NavPoint, end:NavPoint) -> Array[NavPoint]: diff --git a/scripts/instance_data/chest_instance.gd b/scripts/instance_data/chest_instance.gd index 575a708..022de1a 100644 --- a/scripts/instance_data/chest_instance.gd +++ b/scripts/instance_data/chest_instance.gd @@ -3,14 +3,30 @@ extends InstanceData @export var owner:String -@export var loot_table:SKLootTable @export var reset_time_minutes:int @export var owner_id:StringName -var current_inventory:Array[String] +var current_inventory:PackedStringArray func get_archetype_components() -> Array[SKEntityComponent]: return [ + ChestComponent.new(), InventoryComponent.new(), - ChestComponent.new(owner_id, loot_table, reset_time_minutes) ] + + +func convert_to_scene() -> PackedScene: + var ps := PackedScene.new() + + var e := SKEntity.new() + e.name = ref_id + InstanceData._transfer_properties(self, e) + + for c:SKEntityComponent in get_archetype_components(): + InstanceData._transfer_properties(self, c) + e.add_child(c) + c.owner = e + + ps.pack(e) + + return ps diff --git a/scripts/instance_data/instance_data.gd b/scripts/instance_data/instance_data.gd index 1552935..a9b5e7f 100644 --- a/scripts/instance_data/instance_data.gd +++ b/scripts/instance_data/instance_data.gd @@ -1,3 +1,4 @@ +@tool class_name InstanceData extends Resource ## The base class for an instance of an NPC, item, Etc. @@ -19,3 +20,18 @@ func get_archetype_components() -> Array[SKEntityComponent]: ## I'm too tired to explain what this does, but it will return [member override_custom_script] if it isnt null, else it will return the original script. func _try_override_script(sc:Script) -> Script: return sc if override_custom_script == null else override_custom_script + + +func convert_to_scene() -> PackedScene: + return null + + +static func _transfer_properties(from:Object, to:Object) -> void: + var from_p:Array[Dictionary] = from.get_property_list() + var to_p:Array[Dictionary] = to.get_property_list() + + for d:Dictionary in from_p: + if d.name == &"script": + continue + if to_p.any(func(x:Dictionary) -> bool: return d.name == x.name): + to.set(d.name, from.get(d.name)) diff --git a/scripts/instance_data/item_instance.gd b/scripts/instance_data/item_instance.gd index 18d3e60..697d405 100644 --- a/scripts/instance_data/item_instance.gd +++ b/scripts/instance_data/item_instance.gd @@ -1,3 +1,4 @@ +@tool class_name ItemInstance extends InstanceData ## A single instance of an [ItemData] in the world. @@ -14,7 +15,6 @@ func get_archetype_components() -> Array[SKEntityComponent]: var components:Array[SKEntityComponent] = [] # set up item component var item_component = ItemComponent.new() - item_component.data = item_data item_component.contained_inventory = contained_inventory item_component.name = "ItemComponent" # be sure to name them item_component.quest_item = quest_item @@ -27,3 +27,26 @@ func get_archetype_components() -> Array[SKEntityComponent]: components.append(ScriptComponent.new(item_data.custom_script)) return components + + + +func convert_to_scene() -> PackedScene: + var ps := PackedScene.new() + + var e := SKEntity.new() + e.name = item_data.id + InstanceData._transfer_properties(self, e) + + for c:SKEntityComponent in get_archetype_components(): + #InstanceData._transfer_properties(item_data, c) + #InstanceData._transfer_properties(self, c) + e.add_child(c) + c.owner = e + if c is PuppetSpawnerComponent: + var p := item_data.prefab.instantiate() + c.add_child(p) + p.owner = e + + ps.pack(e) + + return ps diff --git a/scripts/instance_data/marker_instance.gd b/scripts/instance_data/marker_instance.gd index e968b86..46d259c 100644 --- a/scripts/instance_data/marker_instance.gd +++ b/scripts/instance_data/marker_instance.gd @@ -1,3 +1,4 @@ +@tool class_name MarkerInstance extends InstanceData @@ -7,3 +8,20 @@ extends InstanceData func get_archetype_components() -> Array[SKEntityComponent]: return [MarkerComponent.new(rotation)] + + +func convert_to_scene() -> PackedScene: + var ps := PackedScene.new() + + var e := SKEntity.new() + e.name = ref_id + InstanceData._transfer_properties(self, e) + + for c:SKEntityComponent in get_archetype_components(): + InstanceData._transfer_properties(self, c) + e.add_child(c) + c.owner = e + + ps.pack(e) + + return ps diff --git a/scripts/instance_data/npc_instance.gd b/scripts/instance_data/npc_instance.gd index 0aceae9..1759dc6 100644 --- a/scripts/instance_data/npc_instance.gd +++ b/scripts/instance_data/npc_instance.gd @@ -1,3 +1,4 @@ +@tool class_name NPCInstance extends InstanceData ## An instance of [NPCData] in the world @@ -11,7 +12,7 @@ extends InstanceData func get_archetype_components() -> Array[SKEntityComponent]: var components:Array[SKEntityComponent] = [] # Add new components - components.append(NPCComponent.new(npc_data)) + components.append(NPCComponent.new()) components.append(InteractiveComponent.new()) components.append(PuppetSpawnerComponent.new()) components.append(TeleportComponent.new()) @@ -30,3 +31,25 @@ func get_archetype_components() -> Array[SKEntityComponent]: components.append(ScriptComponent.new(npc_data.custom_script)) return components + + +func convert_to_scene() -> PackedScene: + var ps := PackedScene.new() + + var e := SKEntity.new() + e.name = ref_id + InstanceData._transfer_properties(self, e) + + var components := get_archetype_components() + for c:SKEntityComponent in components: + InstanceData._transfer_properties(npc_data, c) + e.add_child(c) + c.owner = e + if c is PuppetSpawnerComponent: + var p := npc_data.prefab.instantiate() + c.add_child(p) + p.owner = e + + ps.pack(e) + e.queue_free() + return ps diff --git a/scripts/loottable/items/lt_item.gd b/scripts/loottable/items/lt_item.gd index 881a56d..d8d1e86 100644 --- a/scripts/loottable/items/lt_item.gd +++ b/scripts/loottable/items/lt_item.gd @@ -2,7 +2,7 @@ class_name SKLTItem extends SKLootTableItem -@export var data:ItemData +@export var data:PackedScene func resolve() -> SKLootTable.LootTableResult: diff --git a/scripts/loottable/items/lt_item_entity.gd b/scripts/loottable/items/lt_item_entity.gd new file mode 100644 index 0000000..1ae5e50 --- /dev/null +++ b/scripts/loottable/items/lt_item_entity.gd @@ -0,0 +1,11 @@ +class_name SKLTItemEntity +extends SKLootTableItem + + +## The unique entity to put in the inventory. +@export var item:PackedScene + + +func resolve() -> SKLootTable.LootTableResult: + var id:StringName = item._bundled.names[0] + return SKLootTable.LootTableResult.new([], {}, [id]) diff --git a/scripts/loottable/items/lt_itemchance.gd b/scripts/loottable/items/lt_itemchance.gd index 6775c48..8187308 100644 --- a/scripts/loottable/items/lt_itemchance.gd +++ b/scripts/loottable/items/lt_itemchance.gd @@ -2,7 +2,7 @@ class_name SKLTItemChance extends SKLootTableItem -@export var item:ItemData +@export var item:PackedScene @export_range(0.0, 1.0) var chance:float = 1.0 diff --git a/scripts/loottable/items/lt_itementry.gd b/scripts/loottable/items/lt_itementry.gd index f2c6280..d0c7de6 100644 --- a/scripts/loottable/items/lt_itementry.gd +++ b/scripts/loottable/items/lt_itementry.gd @@ -2,7 +2,7 @@ class_name SKLTItemEntry extends SKLootTableItem -@export var item:ItemData +@export var item:PackedScene func resolve() -> SKLootTable.LootTableResult: diff --git a/scripts/loottable/items/lt_on_condition.gd b/scripts/loottable/items/lt_on_condition.gd index fce9502..b5cfedc 100644 --- a/scripts/loottable/items/lt_on_condition.gd +++ b/scripts/loottable/items/lt_on_condition.gd @@ -3,7 +3,11 @@ extends SKLootTableItem @export_multiline var condition:String = "" -@export var items:SKLootTable +var items:SKLootTable + + +func _ready() -> void: + items = get_child(0) func resolve() -> SKLootTable.LootTableResult: diff --git a/scripts/loottable/items/lt_xofitems.gd b/scripts/loottable/items/lt_xofitems.gd index 7fb9425..ddf1d23 100644 --- a/scripts/loottable/items/lt_xofitems.gd +++ b/scripts/loottable/items/lt_xofitems.gd @@ -4,7 +4,11 @@ extends SKLootTableItem @export_range(0, 100, 1, "or_greater") var x_min:int = 1 @export_range(0, 100, 1, "or_greater") var x_max:int = 0 -@export var items: SKLootTable +var items: SKLootTable + + +func _ready() -> void: + items = get_child(0) func resolve() -> SKLootTable.LootTableResult: diff --git a/scripts/loottable/skloottable.gd b/scripts/loottable/skloottable.gd index 04d81df..c4a3574 100644 --- a/scripts/loottable/skloottable.gd +++ b/scripts/loottable/skloottable.gd @@ -1,11 +1,17 @@ class_name SKLootTable -extends Resource +extends Node ## This is a loot table. It can resolve into a collection of items and currencies. -@export var items:Array[SKLootTableItem] = [] +var items:Array[SKLootTableItem] = [] + + +func _ready() -> void: + items.resize(get_child_count()) + for c:Node in get_children(): + items.append(c) ## Generate all members of the loot table. Returns a dictionary shaped like {&"items":Array[ItemData], &"currencies":{name:amount,...}} @@ -17,16 +23,18 @@ func resolve() -> Dictionary: class LootTableResult: - extends Object + extends RefCounted - var items: Array[ItemData] = [] + var items: Array[PackedScene] = [] var currencies: Dictionary = {} + var entities: Array[StringName] = [] - func _init(i:Array[ItemData] = [], c:Dictionary = {}) -> void: + func _init(i:Array[PackedScene] = [], c:Dictionary = {}, e:Array[StringName] = []) -> void: items = i currencies = c + entities = e func concat(other:LootTableResult) -> void: @@ -36,10 +44,14 @@ class LootTableResult: currencies[c] += other.currencies[c] else: currencies[c] = other.currencies[c] + for id:StringName in other.entities: + if not entities.has(id): + entities.append(id) func to_dict() -> Dictionary: return { &"items": items, - &"currencies": currencies + &"currencies": currencies, + &"entities": entities, } diff --git a/scripts/loottable/skloottableitem.gd b/scripts/loottable/skloottableitem.gd index fba2614..b5a5051 100644 --- a/scripts/loottable/skloottableitem.gd +++ b/scripts/loottable/skloottableitem.gd @@ -1,5 +1,5 @@ class_name SKLootTableItem -extends Resource +extends Node func resolve() -> SKLootTable.LootTableResult: diff --git a/scripts/misc/element_group.gd b/scripts/misc/element_group.gd new file mode 100644 index 0000000..70ff7f2 --- /dev/null +++ b/scripts/misc/element_group.gd @@ -0,0 +1,8 @@ +class_name SKElementGroup +extends Node + + +func _enter_tree() -> void: + while get_child_count() > 0: + get_child(0).reparent(get_parent()) + queue_free() diff --git a/scripts/misc/id_generator.gd b/scripts/misc/id_generator.gd new file mode 100644 index 0000000..6fc88c2 --- /dev/null +++ b/scripts/misc/id_generator.gd @@ -0,0 +1,15 @@ +@tool +class_name SKIDGenerator +extends RefCounted + + +const CHARACTERS: String = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_" + + +static func generate_id(length:int = 10) -> String: + var output := "" + + for _i:int in length: + output += CHARACTERS[randi_range(0, CHARACTERS.length() - 1)] + + return output diff --git a/scripts/misc/npc_template_option.gd b/scripts/misc/npc_template_option.gd index 73d96e6..6b79a81 100644 --- a/scripts/misc/npc_template_option.gd +++ b/scripts/misc/npc_template_option.gd @@ -2,7 +2,7 @@ class_name NPCTemplateOption extends Resource -@export var template:NPCData +@export var template:PackedScene @export_range(0, 1) var chance:float = 1 diff --git a/scripts/points/spawn_point.gd b/scripts/points/spawn_point.gd index 0f4c79a..b6bd6ad 100644 --- a/scripts/points/spawn_point.gd +++ b/scripts/points/spawn_point.gd @@ -2,7 +2,6 @@ class_name NPCSpawnPoint extends Node3D -const UUID = preload("../vendor/uuid.gd") ## This is used for one-shot spawners; the unique ID of the spawner will be stored in here if it ## spawned its NPC. This is a hash set. static var spawn_tracker: Dictionary # TODO: Save this @@ -29,20 +28,13 @@ func _roll() -> void: func spawn() -> void: # set up entity - var t = resolve_templates() + var t := resolve_templates() if t == null: return - var npci = NPCInstance.new() - npci.npc_data = t - npci.world = GameInfo.world - npci.position = position - var uuid:String = UUID.v4() - npci.ref_id = uuid - # add that shiz spawn_tracker[generate_id()] = true - var e = SKEntityManager.instance.add_entity(npci) + var e := SKEntityManager.instance.add_entity_from_scene(t) e.rotation = quaternion e.generated = true if despawn_when_exit_scene: @@ -65,7 +57,7 @@ func generate_id() -> int: ## Roll and select a template -func resolve_templates() -> NPCData: +func resolve_templates() -> PackedScene: if templates.size() == 0: push_warning("No templates to spawn from.") return null diff --git a/scripts/schedules/quest_condition.gd b/scripts/schedules/quest_condition.gd deleted file mode 100644 index 081c8f1..0000000 --- a/scripts/schedules/quest_condition.gd +++ /dev/null @@ -1,20 +0,0 @@ -class_name QuestCondition -extends ScheduleCondition -## Event is only valid if a quest meets a certain criteria. - - -## Step or quest ID. Step must be in format QuestID/StepID. -@export var id:String -## Check if the quest is... -@export_enum("active", "completed", "not started") var status:int - - -func evaluate() -> bool: - match status: - 0: - return SkeleRealmsGlobal.quest_engine.is_member_active(id) - 1: - return SkeleRealmsGlobal.quest_engine.is_member_complete(id) - 2: - return not (SkeleRealmsGlobal.quest_engine.has_member_been_started(id)) - return false diff --git a/scripts/schedules/schedule.gd b/scripts/schedules/schedule.gd index be68042..8a54da1 100644 --- a/scripts/schedules/schedule.gd +++ b/scripts/schedules/schedule.gd @@ -1,12 +1,14 @@ class_name Schedule extends Node + + ## Keeps track of the schedule. ## Schedules are roughly analagous to Creation Kit's "AI packages", although limited to time slots. ## It is made up of [ScheduleEvent]s. ## To adjust NPC behavior under circumastances outside of keeping a schedule, see [GOAPComponent] and [ScheduleCondition]. -@export var events:Array[ScheduleEvent] +@onready var events:Array[ScheduleEvent] = get_children() func find_schedule_activity_for_current_time() -> Option: diff --git a/scripts/schedules/schedule_event.gd b/scripts/schedules/schedule_event.gd index 7e054ea..ca66dbb 100644 --- a/scripts/schedules/schedule_event.gd +++ b/scripts/schedules/schedule_event.gd @@ -1,27 +1,35 @@ class_name ScheduleEvent -extends Resource +extends Node + + +## These are the different schedule events that can occupy a schedule. -@export var name:String ## From what time? @export var from:Timestamp ## To what time? @export var to:Timestamp +## Anmy condition that needs be checked first. @export var condition:ScheduleCondition +## Schedule priotity. @export var priority:float -## Get the location this event is at +## Get the location this event is at. func get_event_location() -> NavPoint: return null +## Wthether this entity is "at" the event. func satisfied_at_location(e:SKEntity) -> bool: return true + +## What to do when the event has begun. func on_event_started() -> void: return +## What to do when the event has ended. func on_event_ended() -> void: return diff --git a/scripts/sk_global.gd b/scripts/sk_global.gd index c8459bd..50e78b1 100644 --- a/scripts/sk_global.gd +++ b/scripts/sk_global.gd @@ -3,8 +3,6 @@ extends Node ## It also has some important utility functions for working with entities. -## Reference to the [QuestEngine]. -var quest_engine:QuestEngine ## World states for the GOAP system. var world_states:Dictionary ## Status effects registered in the game. diff --git a/scripts/system/game_info.gd b/scripts/system/game_info.gd index d88e4a9..4a2b527 100644 --- a/scripts/system/game_info.gd +++ b/scripts/system/game_info.gd @@ -3,7 +3,7 @@ extends Node ## What world the player is in. -@export var world: String = "init" +@export var world: StringName = &"init" var is_loading:bool = false var paused:bool = false diff --git a/scripts/vendor/uuid.gd b/scripts/vendor/uuid.gd deleted file mode 100644 index 3d094b4..0000000 --- a/scripts/vendor/uuid.gd +++ /dev/null @@ -1,44 +0,0 @@ -### THIS CODE IS TAKEN FROM BINOGURE'S UUID LIBRARY! ### -### https://github.com/binogure-studio/godot-uuid ### - -# Note: The code might not be as pretty it could be, since it's written -# in a way that maximizes performance. Methods are inlined and loops are avoided. -extends Node - -const MODULO_8_BIT = 256 - -static func getRandomInt(): - # Randomize every time to minimize the risk of collisions - randomize() - - return randi() % MODULO_8_BIT - -static func uuidbin(): - # 16 random bytes with the bytes on index 6 and 8 modified - return [ - getRandomInt(), getRandomInt(), getRandomInt(), getRandomInt(), - getRandomInt(), getRandomInt(), ((getRandomInt()) & 0x0f) | 0x40, getRandomInt(), - ((getRandomInt()) & 0x3f) | 0x80, getRandomInt(), getRandomInt(), getRandomInt(), - getRandomInt(), getRandomInt(), getRandomInt(), getRandomInt(), - ] - -static func v4(): - # 16 random bytes with the bytes on index 6 and 8 modified - var b = uuidbin() - - return '%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x' % [ - # low - b[0], b[1], b[2], b[3], - - # mid - b[4], b[5], - - # hi - b[6], b[7], - - # clock - b[8], b[9], - - # clock - b[10], b[11], b[12], b[13], b[14], b[15] - ] diff --git a/scripts/world_objects/item_gizmo.gd b/scripts/world_objects/item_gizmo.gd index 5fa8529..3b84006 100644 --- a/scripts/world_objects/item_gizmo.gd +++ b/scripts/world_objects/item_gizmo.gd @@ -1,4 +1,3 @@ -class_name WorldItemGizmo extends EditorNode3DGizmoPlugin diff --git a/scripts/world_objects/npc_gizmo.gd b/scripts/world_objects/npc_gizmo.gd index db016b3..8cdf7c4 100644 --- a/scripts/world_objects/npc_gizmo.gd +++ b/scripts/world_objects/npc_gizmo.gd @@ -1,4 +1,3 @@ -class_name WorldNPCGizmo extends EditorNode3DGizmoPlugin diff --git a/scripts/world_objects/world_entity.gd b/scripts/world_objects/world_entity.gd new file mode 100644 index 0000000..c982a4b --- /dev/null +++ b/scripts/world_objects/world_entity.gd @@ -0,0 +1,50 @@ +@tool +class_name SKWorldEntity +extends Marker3D + + +@export var entity:PackedScene: + set(val): + entity = val + if Engine.is_editor_hint(): + if get_child_count() > 0: + get_child(0) + if val: + _show_preview() + + +func _ready() -> void: + if Engine.is_editor_hint(): + _show_preview() + else: + SKEntityManager.instance.get_entity(entity._bundled.names[0]) + + +func _show_preview() -> void: + if not entity: + return + var e:SKEntity = entity.instantiate() + var n:Node = e.get_world_entity_preview().duplicate() + e.queue_free() + if not n: + return + add_child(n) + + +func _sync() -> void: + if not Engine.is_editor_hint(): + return + + if not entity: + return + + var e:SKEntity = entity.instantiate() + if not e: + return + + e.position = global_position + e.world = EditorInterface.get_edited_scene_root().name + + print(e.world) + + entity.pack(e) diff --git a/scripts/world_objects/world_object_plugin.gd b/scripts/world_objects/world_object_plugin.gd index e6f6995..450399d 100644 --- a/scripts/world_objects/world_object_plugin.gd +++ b/scripts/world_objects/world_object_plugin.gd @@ -1,4 +1,3 @@ -class_name WorldObjectPlugin extends EditorInspectorPlugin diff --git a/skelerealms.gd b/skelerealms.gd index cefe178..68c666b 100644 --- a/skelerealms.gd +++ b/skelerealms.gd @@ -2,27 +2,29 @@ extends EditorPlugin -var item_gizmo_plugin = WorldItemGizmo.new() -var npc_gizmo_plugin = WorldNPCGizmo.new() -var door_jump_plugin = DoorJumpPlugin.new(self) -var world_thing_plugin = WorldObjectPlugin.new() -var point_gizmo = PointGizmo.new() -var tool_editor = SKToolPlugin.new() -var prototype_editor = PrototypePlugin.new() +const DoorJumpPlugin = preload("res://addons/skelerealms/tools/door_connect.gd") +const ConversionPlugin = preload("res://addons/skelerealms/tools/resource_conversion_plugin.gd") +const WorldEntityPlugin = preload("res://addons/skelerealms/tools/world_entity_plugin.gd") +const PointGizmo = preload("res://addons/skelerealms/tools/point_gizmo.gd") +const ScheduleEditorPlugin = preload("res://addons/skelerealms/tools/schedule_editor_plugin.gd") -var fd:EditorFileDialog -var p:Prototype +var door_jump_plugin := DoorJumpPlugin.new(self) +var conversion := ConversionPlugin.new() +var we_plugin := WorldEntityPlugin.new() +var point_gizmo := PointGizmo.new() +var se_plugin := ScheduleEditorPlugin.new() + +var se_w: Window +var se: Control func _enter_tree(): # gizmos - add_node_3d_gizmo_plugin(item_gizmo_plugin) - add_node_3d_gizmo_plugin(npc_gizmo_plugin) add_node_3d_gizmo_plugin(point_gizmo) add_inspector_plugin(door_jump_plugin) - add_inspector_plugin(world_thing_plugin) - add_inspector_plugin(tool_editor) - add_inspector_plugin(prototype_editor) + add_inspector_plugin(conversion) + add_inspector_plugin(we_plugin) + add_inspector_plugin(se_plugin) # autoload add_autoload_singleton("SkeleRealmsGlobal", "res://addons/skelerealms/scripts/sk_global.gd") add_autoload_singleton("CovenSystem", "res://addons/skelerealms/scripts/covens/coven_system.gd") @@ -31,25 +33,26 @@ func _enter_tree(): add_autoload_singleton("CrimeMaster", "res://addons/skelerealms/scripts/crime/crime_master.gd") add_autoload_singleton("DialogueHooks", "res://addons/skelerealms/scripts/system/dialogue_hooks.gd") add_autoload_singleton("DeviceNetwork", "res://addons/skelerealms/scripts/misc/device_network.gd") - # else - fd = EditorFileDialog.new() - fd.file_mode = EditorFileDialog.FILE_MODE_SAVE_FILE - fd.filters = PackedStringArray(["*.tres", "*.res"]) - # TODO: Modes - EditorInterface.get_base_control().add_child(fd) - prototype_editor.inspect.connect(instantiate_prototype.bind()) - fd.file_selected.connect(finish_instantiate.bind()) + + se_w = Window.new() + se = ScheduleEditorPlugin.ScheduleEditor.instantiate() + se_w.add_child(se) + EditorInterface.get_base_control().add_child(se_w) + se_w.hide() + + se_plugin.request_open.connect(func(events:Array[ScheduleEvent]) -> void: + se.edit(events) + se_w.popup_centered(Vector2i(1920, 1080)) + ) func _exit_tree(): # gizmos - remove_node_3d_gizmo_plugin(item_gizmo_plugin) - remove_node_3d_gizmo_plugin(npc_gizmo_plugin) remove_node_3d_gizmo_plugin(point_gizmo) remove_inspector_plugin(door_jump_plugin) - remove_inspector_plugin(world_thing_plugin) - remove_inspector_plugin(tool_editor) - remove_import_plugin(prototype_editor) + remove_inspector_plugin(conversion) + remove_inspector_plugin(we_plugin) + remove_inspector_plugin(se_plugin) # autoload remove_autoload_singleton("SkeleRealmsGlobal") remove_autoload_singleton("CovenSystem") @@ -58,6 +61,8 @@ func _exit_tree(): remove_autoload_singleton("CrimeMaster") remove_autoload_singleton("DialogueHooks") remove_autoload_singleton("DeviceNetwork") + + se_w.queue_free() func _enable_plugin() -> void: @@ -79,6 +84,11 @@ func _enable_plugin() -> void: ProjectSettings.set_setting("skelerealms/covens_path", "res://covens") ProjectSettings.set_setting("skelerealms/doors_path", "res://doors") ProjectSettings.set_setting("skelerealms/networks_path", "res://networks") + + ProjectSettings.set_setting("skelerealms/entity_archetypes", PackedStringArray([ + "res://addons/skelerealms/npc_entity_template.tscn", + "res://addons/skelerealms/item_entity_template.tscn" + ])) func _disable_plugin() -> void: @@ -100,16 +110,5 @@ func _disable_plugin() -> void: ProjectSettings.set_setting("skelerealms/covens_path", null) ProjectSettings.set_setting("skelerealms/doors_path", null) ProjectSettings.set_setting("skelerealms/networks_path", null) - - -func instantiate_prototype(object:Prototype) -> void: - fd.popup_centered() - p = object - - -func finish_instantiate(path:String) -> void: - var slashes:Array = path.split("/") - var dots = slashes[slashes.size() - 1].split(".") - var n:String = dots[0] - # Could do a regex but whatever - p.instantiate_prototype(path, n) + + ProjectSettings.set_setting("skelerealms/entity_archetypes", null) diff --git a/tools/coven_drag_target.gd b/tools/coven_drag_target.gd deleted file mode 100644 index 60a36a9..0000000 --- a/tools/coven_drag_target.gd +++ /dev/null @@ -1,20 +0,0 @@ -@tool -extends Label - - -func _can_drop_data(_at_position: Vector2, data: Variant) -> bool: - if not data is Dictionary: - return false - if not data.has("files"): - return false - if not data.files.size() == 0: - return false - return data.files.all(func(x:String) -> bool: return x.ends_with(".tscn") or x.ends_with(".res")) - - -func _drop_data(_at_position: Vector2, data: Variant) -> void: - EditorInterface.get_resource_previewer().queue_resource_preview(data.files[0], self, &"get_thumb", null) - - -func get_thumb(path:String, _preview:Texture2D, thumb:Texture2D, _ud:Variant) -> void: - text = path diff --git a/tools/coven_editor.gd b/tools/coven_editor.gd deleted file mode 100644 index a76da3e..0000000 --- a/tools/coven_editor.gd +++ /dev/null @@ -1,85 +0,0 @@ -@tool -extends Control - - -@onready var rank_list:ItemList = $HBoxContainer/VBoxContainer/Ranks -var editing:Coven - - -func edit(c:Coven) -> void: - editing = c - $HBoxContainer/VBoxContainer/GridContainer/Hidden.button_pressed = c.hidden_from_player - $HBoxContainer/VBoxContainer/GridContainer/IgnoreOthers.button_pressed = c.ignore_crimes_against_others - $HBoxContainer/VBoxContainer/GridContainer/IgnoreMembers.button_pressed = c.ignore_crimes_against_members - $HBoxContainer/VBoxContainer/GridContainer/TrackCrime.button_pressed = c.track_crime - rank_list = $HBoxContainer/VBoxContainer/Ranks - for r:int in c.ranks: - rank_list.add_item(c.ranks[r]) - for o:StringName in c.other_coven_opinions: - _add_opinion_to_list(o, c.other_coven_opinions[o]) - $HBoxContainer/VBoxContainer/LineEdit.text = c.coven_id - - -func _on_add_opinion_pressed() -> void: - _add_opinion_to_list(&"", 0) - - -func _on_add_rank_pressed() -> void: - var rn:LineEdit = $HBoxContainer/VBoxContainer/HBoxContainer/RankName - if rn.text.is_empty(): - return - rank_list.add_item(rn.text) - rn.clear() - _update_rank_list() - - -func _on_remove_rank_pressed() -> void: - if rank_list.is_anything_selected(): - rank_list.remove_item(rank_list.get_selected_items()[0]) - _update_rank_list() - - -func _update_rank_list() -> void: - editing.ranks.clear() - for i:int in rank_list.item_count: - editing.ranks[i] = rank_list.get_item_text(i) - - -func _add_opinion_to_list(c:StringName, o:int) -> void: - var container:HBoxContainer = HBoxContainer.new() - - var c_opinion:SpinBox = SpinBox.new() - c_opinion.rounded = true - - var c_name:LineEdit = LineEdit.new() - c_name.placeholder_text = "Coven Name..." - c_name.size_flags_horizontal = Control.SIZE_EXPAND_FILL - c_name.text_submitted.connect(func(new_text:String) -> void: - editing.other_coven_opinions.erase(c_name.text) - editing.other_coven_opinions[new_text] = roundi(c_opinion.value) - ) - - c_opinion.value_changed.connect(func(new_value:float) -> void: - editing.other_coven_opinions[c_name.text] = roundi(new_value) - ) - - if not c.is_empty(): - c_name.text = String(c) - c_opinion.value = o - - var rb:Button = Button.new() - rb.text = "Remove" - rb.pressed.connect(func() -> void: - container.queue_free() - editing.other_coven_opinions.erase(c_name.text) - ) - - container.add_child(c_name) - container.add_child(c_opinion) - container.add_child(rb) - - $HBoxContainer/VBoxContainer2/Opinions.add_child(container) - - -func _on_line_edit_text_changed(new_text: String) -> void: - editing.coven_id = new_text diff --git a/tools/coven_editor.tscn b/tools/coven_editor.tscn deleted file mode 100644 index 2a6fb46..0000000 --- a/tools/coven_editor.tscn +++ /dev/null @@ -1,95 +0,0 @@ -[gd_scene load_steps=2 format=3 uid="uid://1yaewgwfg00q"] - -[ext_resource type="Script" path="res://addons/skelerealms/tools/coven_editor.gd" id="1_kfrhe"] - -[node name="CovenEditor" type="PanelContainer"] -anchors_preset = 15 -anchor_right = 1.0 -anchor_bottom = 1.0 -grow_horizontal = 2 -grow_vertical = 2 -script = ExtResource("1_kfrhe") - -[node name="HBoxContainer" type="HBoxContainer" parent="."] -layout_mode = 2 - -[node name="VBoxContainer" type="VBoxContainer" parent="HBoxContainer"] -layout_mode = 2 -size_flags_horizontal = 3 - -[node name="LineEdit" type="LineEdit" parent="HBoxContainer/VBoxContainer"] -layout_mode = 2 -placeholder_text = "Coven ID" - -[node name="GridContainer" type="GridContainer" parent="HBoxContainer/VBoxContainer"] -layout_mode = 2 -columns = 2 - -[node name="Hidden" type="CheckBox" parent="HBoxContainer/VBoxContainer/GridContainer"] -layout_mode = 2 -text = "Hidden From Player" - -[node name="IgnoreOthers" type="CheckBox" parent="HBoxContainer/VBoxContainer/GridContainer"] -layout_mode = 2 -text = "Ignore Crimes Against Others" - -[node name="IgnoreMembers" type="CheckBox" parent="HBoxContainer/VBoxContainer/GridContainer"] -layout_mode = 2 -text = "Ignore Crimes Against Members" - -[node name="TrackCrime" type="CheckBox" parent="HBoxContainer/VBoxContainer/GridContainer"] -layout_mode = 2 -text = "Track Crime" - -[node name="HBoxContainer" type="HBoxContainer" parent="HBoxContainer/VBoxContainer"] -layout_mode = 2 - -[node name="Label2" type="Label" parent="HBoxContainer/VBoxContainer/HBoxContainer"] -layout_mode = 2 -size_flags_horizontal = 3 -text = "Ranks (Rank 0 on Bottom)" - -[node name="RankName" type="LineEdit" parent="HBoxContainer/VBoxContainer/HBoxContainer"] -layout_mode = 2 -placeholder_text = "Rank Name..." - -[node name="AddRank" type="Button" parent="HBoxContainer/VBoxContainer/HBoxContainer"] -layout_mode = 2 -text = "Add" - -[node name="RemoveRank" type="Button" parent="HBoxContainer/VBoxContainer/HBoxContainer"] -layout_mode = 2 -text = "Remove Selected Rank" - -[node name="Ranks" type="ItemList" parent="HBoxContainer/VBoxContainer"] -layout_mode = 2 -size_flags_vertical = 3 - -[node name="VBoxContainer2" type="VBoxContainer" parent="HBoxContainer"] -layout_mode = 2 -size_flags_horizontal = 3 - -[node name="HBoxContainer" type="HBoxContainer" parent="HBoxContainer/VBoxContainer2"] -layout_mode = 2 - -[node name="Label" type="Label" parent="HBoxContainer/VBoxContainer2/HBoxContainer"] -layout_mode = 2 -size_flags_horizontal = 3 -text = "Opinions" - -[node name="AddOpinion" type="Button" parent="HBoxContainer/VBoxContainer2/HBoxContainer"] -layout_mode = 2 -text = "Add" - -[node name="Opinions" type="VBoxContainer" parent="HBoxContainer/VBoxContainer2"] -layout_mode = 2 -size_flags_vertical = 3 - -[connection signal="text_changed" from="HBoxContainer/VBoxContainer/LineEdit" to="." method="_on_line_edit_text_changed"] -[connection signal="pressed" from="HBoxContainer/VBoxContainer/GridContainer/Hidden" to="." method="_on_hidden_pressed"] -[connection signal="pressed" from="HBoxContainer/VBoxContainer/GridContainer/IgnoreOthers" to="." method="_on_ignore_others_pressed"] -[connection signal="pressed" from="HBoxContainer/VBoxContainer/GridContainer/IgnoreMembers" to="." method="_on_ignore_members_pressed"] -[connection signal="pressed" from="HBoxContainer/VBoxContainer/GridContainer/TrackCrime" to="." method="_on_track_crime_pressed"] -[connection signal="pressed" from="HBoxContainer/VBoxContainer/HBoxContainer/AddRank" to="." method="_on_add_rank_pressed"] -[connection signal="pressed" from="HBoxContainer/VBoxContainer/HBoxContainer/RemoveRank" to="." method="_on_remove_rank_pressed"] -[connection signal="pressed" from="HBoxContainer/VBoxContainer2/HBoxContainer/AddOpinion" to="." method="_on_add_opinion_pressed"] diff --git a/tools/coven_rank_data_editor.gd b/tools/coven_rank_data_editor.gd deleted file mode 100644 index 88165d9..0000000 --- a/tools/coven_rank_data_editor.gd +++ /dev/null @@ -1,34 +0,0 @@ -@tool -extends PanelContainer - - -var editing:CovenRankData - -signal delete_requested - - -func edit(c:CovenRankData) -> void: - editing = c - _load() - - -func _load() -> void: - $HBoxContainer/VBoxContainer/HBoxContainer/SpinBox.value = editing.rank - if not editing.coven == null: - $HBoxContainer/VBoxContainer/Coven/VBoxContainer/Label.text = editing.coven.resource_path - - -func open_coven_editor() -> void: - SKToolPlugin.instance._open_window(editing.coven) - - -func _on_button_pressed() -> void: - open_coven_editor() - - -func _on_spin_box_value_changed(value: float) -> void: - editing.rank = floori(value) - - -func _on_delete_pressed() -> void: - delete_requested.emit() diff --git a/tools/coven_rank_data_editor.tscn b/tools/coven_rank_data_editor.tscn deleted file mode 100644 index 7858665..0000000 --- a/tools/coven_rank_data_editor.tscn +++ /dev/null @@ -1,51 +0,0 @@ -[gd_scene load_steps=3 format=3 uid="uid://dvudb1s4sw5hs"] - -[ext_resource type="Script" path="res://addons/skelerealms/tools/coven_rank_data_editor.gd" id="1_4omcm"] -[ext_resource type="Script" path="res://addons/skelerealms/tools/coven_drag_target.gd" id="2_w6wt6"] - -[node name="CovenRankDataEditor" type="PanelContainer"] -anchors_preset = 15 -anchor_right = 1.0 -anchor_bottom = 1.0 -grow_horizontal = 2 -grow_vertical = 2 -script = ExtResource("1_4omcm") - -[node name="HBoxContainer" type="HBoxContainer" parent="."] -layout_mode = 2 - -[node name="VBoxContainer" type="VBoxContainer" parent="HBoxContainer"] -layout_mode = 2 -size_flags_horizontal = 3 - -[node name="HBoxContainer" type="HBoxContainer" parent="HBoxContainer/VBoxContainer"] -layout_mode = 2 - -[node name="Label" type="Label" parent="HBoxContainer/VBoxContainer/HBoxContainer"] -layout_mode = 2 -text = "Rank" - -[node name="SpinBox" type="SpinBox" parent="HBoxContainer/VBoxContainer/HBoxContainer"] -layout_mode = 2 - -[node name="Coven" type="PanelContainer" parent="HBoxContainer/VBoxContainer"] -layout_mode = 2 - -[node name="VBoxContainer" type="VBoxContainer" parent="HBoxContainer/VBoxContainer/Coven"] -layout_mode = 2 - -[node name="Label" type="Label" parent="HBoxContainer/VBoxContainer/Coven/VBoxContainer"] -layout_mode = 2 -text = "Coven (Drag from filesystem to load)" -script = ExtResource("2_w6wt6") - -[node name="Button" type="Button" parent="HBoxContainer/VBoxContainer/Coven/VBoxContainer"] -layout_mode = 2 -text = "Open Coven Editor" - -[node name="Delete" type="Button" parent="HBoxContainer"] -layout_mode = 2 -text = "Delete" - -[connection signal="pressed" from="HBoxContainer/VBoxContainer/Coven/VBoxContainer/Button" to="." method="open_coven_editor"] -[connection signal="pressed" from="HBoxContainer/Delete" to="." method="_on_delete_pressed"] diff --git a/scripts/world_objects/door_connect.gd b/tools/door_connect.gd similarity index 99% rename from scripts/world_objects/door_connect.gd rename to tools/door_connect.gd index 7fb6ffb..35af952 100644 --- a/scripts/world_objects/door_connect.gd +++ b/tools/door_connect.gd @@ -1,4 +1,3 @@ -class_name DoorJumpPlugin extends EditorInspectorPlugin diff --git a/tools/instance_inspector_widget.gd b/tools/instance_inspector_widget.gd deleted file mode 100644 index ebadd7a..0000000 --- a/tools/instance_inspector_widget.gd +++ /dev/null @@ -1,34 +0,0 @@ -@tool -extends HBoxContainer - - -var editing:InstanceData - - -func edit(i:InstanceData) -> void: - editing = i - $RefID.text = i.ref_id - $World.text = i.world - $X.value = i.position.x - $Y.value = i.position.y - $Z.value = i.position.z - - -func _on_ref_id_text_submitted(new_text: String) -> void: - editing.ref_id = new_text - - -func _on_world_text_submitted(new_text: String) -> void: - editing.world = StringName(new_text) - - -func _on_x_value_changed(value: float) -> void: - editing.position.x = value - - -func _on_y_value_changed(value: float) -> void: - editing.position.y = value - - -func _on_z_value_changed(value: float) -> void: - editing.position.z = value diff --git a/tools/instance_inspector_widget.tscn b/tools/instance_inspector_widget.tscn deleted file mode 100644 index 268c318..0000000 --- a/tools/instance_inspector_widget.tscn +++ /dev/null @@ -1,66 +0,0 @@ -[gd_scene load_steps=5 format=3 uid="uid://ljmha04rl7i3"] - -[ext_resource type="Script" path="res://addons/skelerealms/tools/instance_inspector_widget.gd" id="1_7o5m0"] - -[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_0bpsc"] -bg_color = Color(0.862745, 0.0941176, 0.286275, 1) - -[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_xuil4"] -bg_color = Color(0.317647, 0.705882, 0, 1) - -[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_qg5rk"] -bg_color = Color(0, 0.482353, 0.752941, 1) - -[node name="HBoxContainer" type="HBoxContainer"] -offset_right = 40.0 -offset_bottom = 40.0 -script = ExtResource("1_7o5m0") - -[node name="RefID" type="LineEdit" parent="."] -layout_mode = 2 -placeholder_text = "RefID" -expand_to_text_length = true - -[node name="World" type="LineEdit" parent="."] -layout_mode = 2 -placeholder_text = "World" -expand_to_text_length = true - -[node name="Panel" type="PanelContainer" parent="."] -layout_mode = 2 -theme_override_styles/panel = SubResource("StyleBoxFlat_0bpsc") - -[node name="Label" type="Label" parent="Panel"] -layout_mode = 2 -text = "X" - -[node name="X" type="SpinBox" parent="."] -layout_mode = 2 - -[node name="Panel2" type="PanelContainer" parent="."] -layout_mode = 2 -theme_override_styles/panel = SubResource("StyleBoxFlat_xuil4") - -[node name="Label" type="Label" parent="Panel2"] -layout_mode = 2 -text = "Y" - -[node name="Y" type="SpinBox" parent="."] -layout_mode = 2 - -[node name="Panel3" type="PanelContainer" parent="."] -layout_mode = 2 -theme_override_styles/panel = SubResource("StyleBoxFlat_qg5rk") - -[node name="Label" type="Label" parent="Panel3"] -layout_mode = 2 -text = "Z" - -[node name="Z" type="SpinBox" parent="."] -layout_mode = 2 - -[connection signal="text_submitted" from="RefID" to="." method="_on_ref_id_text_submitted"] -[connection signal="text_submitted" from="World" to="." method="_on_world_text_submitted"] -[connection signal="value_changed" from="X" to="." method="_on_x_value_changed"] -[connection signal="value_changed" from="Y" to="." method="_on_y_value_changed"] -[connection signal="value_changed" from="Z" to="." method="_on_z_value_changed"] diff --git a/tools/item_data_inspector.gd b/tools/item_data_inspector.gd deleted file mode 100644 index a7166a0..0000000 --- a/tools/item_data_inspector.gd +++ /dev/null @@ -1,120 +0,0 @@ -@tool -extends PanelContainer - - -@onready var fd:FileDialog = $FileDialog -@onready var class_list:OptionButton = $HBoxContainer/VBoxContainer2/HBoxContainer/ComponentsList -@onready var tag_line:LineEdit = $HBoxContainer/VBoxContainer/VBoxContainer/LineEdit -var editing:ItemData - - -func _ready() -> void: - fd.files_selected.connect(func(files:PackedStringArray) -> void: - for f:String in files: - var n:ItemDataComponent = load(f) - if not n: - return - _add_component_to_list(n) - editing.components.append(n) - ) - class_list.clear() - var inherited:Array = SKToolPlugin.find_classes_that_inherit(&"ItemDataComponent") - for d:Dictionary in inherited: - class_list.add_item(d.class) - class_list.set_item_metadata(class_list.item_count - 1, d.path) - - -func edit(e:ItemData) -> void: - editing = e - $HBoxContainer/VBoxContainer/HBoxContainer3/Stack.pressed = e.stackable - $HBoxContainer/VBoxContainer/HBoxContainer2/Worth.value = e.worth as float - $HBoxContainer/VBoxContainer/HBoxContainer/Weight.value = e.weight as float - $HBoxContainer/VBoxContainer3/Prefab.set_to_scene(e.prefab.resource_path) - $HBoxContainer/VBoxContainer3/HeldItem.set_to_scene(e.hand_item.resource_path) - $HBoxContainer/VBoxContainer/BaseID.text = e.id - for t:StringName in e.tags: - _add_tag_to_list(t) - for c:ItemDataComponent in e.components: - _add_component_to_list(c) - - -func _on_held_item_prefab_set(path: String) -> void: - if path.is_empty(): - editing.hand_item = null - else: - editing.hand_item = load(path) - - -func _on_prefab_prefab_set(path: String) -> void: - if path.is_empty(): - editing.prefab = null - else: - editing.prefab = load(path) - - -func _on_open_component_pressed() -> void: - fd.popup_centered() - - -func _on_add_component_pressed() -> void: - var path:String = class_list.get_item_metadata(class_list.selected) - var n:ItemDataComponent = load(path).new() - _add_component_to_list(n) - editing.components.append(n) - - -func _add_component_to_list(ic:ItemDataComponent) -> void: - var container:HBoxContainer = HBoxContainer.new() - var l:Label = Label.new() - l.text = ic.get_type() # may nt work - var rb:Button = Button.new() - rb.text = "Remove" - rb.pressed.connect(func() -> void: - container.queue_free() - editing.components.erase(ic) - ) - var ib:Button = Button.new() - ib.text = "Open in Inspector" - ib.pressed.connect(func() -> void: - EditorInterface.edit_resource(ic) - ) - - container.add_child(l) - container.add_child(rb) - container.add_child(ib) - - -func _on_add_tag_pressed() -> void: - var tag:StringName = StringName(tag_line.text) - tag_line.clear() - if not editing.tags.has(tag): - _add_tag_to_list(tag) - editing.tags.append(tag) - - -func _add_tag_to_list(tag:StringName) -> void: - var container:HBoxContainer = HBoxContainer.new() - var l:Label = Label.new() - l.text = String(tag) - var rb:Button = Button.new() - rb.text = "Remove" - rb.pressed.connect(func() -> void: - container.queue_free() - editing.tags.erase(tag) - ) - - -func _on_base_id_text_submitted(new_text: String) -> void: - editing.id = new_text - - -func _on_weight_value_changed(value: float) -> void: - editing.weight = roundi(value) - - -func _on_worth_value_changed(value: float) -> void: - editing.worth = roundi(value) - - -func _on_stack_toggled(toggled_on: bool) -> void: - editing.stackable = toggled_on diff --git a/tools/item_data_inspector.tscn b/tools/item_data_inspector.tscn deleted file mode 100644 index 196ba1a..0000000 --- a/tools/item_data_inspector.tscn +++ /dev/null @@ -1,133 +0,0 @@ -[gd_scene load_steps=3 format=3 uid="uid://dfxye68yntvkf"] - -[ext_resource type="PackedScene" uid="uid://bsj34wkkfoi3p" path="res://addons/skelerealms/tools/prefab_selector.tscn" id="1_luwau"] -[ext_resource type="Script" path="res://addons/skelerealms/tools/item_data_inspector.gd" id="1_ygblm"] - -[node name="ItemDataInspector" type="PanelContainer"] -anchors_preset = 15 -anchor_right = 1.0 -anchor_bottom = 1.0 -grow_horizontal = 2 -grow_vertical = 2 -script = ExtResource("1_ygblm") - -[node name="HBoxContainer" type="HBoxContainer" parent="."] -layout_mode = 2 - -[node name="VBoxContainer" type="VBoxContainer" parent="HBoxContainer"] -layout_mode = 2 -size_flags_horizontal = 3 - -[node name="HBoxContainer" type="HBoxContainer" parent="HBoxContainer/VBoxContainer"] -layout_mode = 2 - -[node name="Label" type="Label" parent="HBoxContainer/VBoxContainer/HBoxContainer"] -layout_mode = 2 -text = "Weight" - -[node name="Weight" type="SpinBox" parent="HBoxContainer/VBoxContainer/HBoxContainer"] -layout_mode = 2 -allow_greater = true - -[node name="HBoxContainer2" type="HBoxContainer" parent="HBoxContainer/VBoxContainer"] -layout_mode = 2 - -[node name="Label" type="Label" parent="HBoxContainer/VBoxContainer/HBoxContainer2"] -layout_mode = 2 -text = "Worth" - -[node name="Worth" type="SpinBox" parent="HBoxContainer/VBoxContainer/HBoxContainer2"] -layout_mode = 2 -rounded = true -allow_greater = true - -[node name="HBoxContainer3" type="HBoxContainer" parent="HBoxContainer/VBoxContainer"] -layout_mode = 2 - -[node name="Label" type="Label" parent="HBoxContainer/VBoxContainer/HBoxContainer3"] -layout_mode = 2 -text = "Stackable" - -[node name="Stack" type="CheckBox" parent="HBoxContainer/VBoxContainer/HBoxContainer3"] -layout_mode = 2 - -[node name="BaseID" type="LineEdit" parent="HBoxContainer/VBoxContainer"] -layout_mode = 2 -placeholder_text = "BaseID" - -[node name="VBoxContainer" type="VBoxContainer" parent="HBoxContainer/VBoxContainer"] -layout_mode = 2 - -[node name="Label" type="Label" parent="HBoxContainer/VBoxContainer/VBoxContainer"] -layout_mode = 2 -text = "Tags" - -[node name="LineEdit" type="LineEdit" parent="HBoxContainer/VBoxContainer/VBoxContainer"] -layout_mode = 2 -placeholder_text = "Tag" - -[node name="AddTag" type="Button" parent="HBoxContainer/VBoxContainer/VBoxContainer"] -layout_mode = 2 -text = "Add Tag" - -[node name="VBoxContainer" type="VBoxContainer" parent="HBoxContainer/VBoxContainer/VBoxContainer"] -layout_mode = 2 - -[node name="VBoxContainer2" type="VBoxContainer" parent="HBoxContainer"] -layout_mode = 2 -size_flags_horizontal = 3 - -[node name="Label" type="Label" parent="HBoxContainer/VBoxContainer2"] -layout_mode = 2 -text = "Components" - -[node name="HBoxContainer" type="HBoxContainer" parent="HBoxContainer/VBoxContainer2"] -layout_mode = 2 - -[node name="AddComponent" type="Button" parent="HBoxContainer/VBoxContainer2/HBoxContainer"] -layout_mode = 2 -text = "Add" - -[node name="OpenComponent" type="Button" parent="HBoxContainer/VBoxContainer2/HBoxContainer"] -layout_mode = 2 -text = "Load From File" - -[node name="ComponentsList" type="OptionButton" parent="HBoxContainer/VBoxContainer2/HBoxContainer"] -layout_mode = 2 - -[node name="Components" type="VBoxContainer" parent="HBoxContainer/VBoxContainer2"] -layout_mode = 2 - -[node name="VBoxContainer3" type="VBoxContainer" parent="HBoxContainer"] -layout_mode = 2 -size_flags_horizontal = 3 - -[node name="Label" type="Label" parent="HBoxContainer/VBoxContainer3"] -layout_mode = 2 -text = "Prefab" - -[node name="Prefab" parent="HBoxContainer/VBoxContainer3" instance=ExtResource("1_luwau")] -layout_mode = 2 - -[node name="Label2" type="Label" parent="HBoxContainer/VBoxContainer3"] -layout_mode = 2 -text = "Hand Item (Optional)" - -[node name="HeldItem" parent="HBoxContainer/VBoxContainer3" instance=ExtResource("1_luwau")] -layout_mode = 2 - -[node name="FileDialog" type="FileDialog" parent="."] -title = "Open File(s)" -ok_button_text = "Open" -file_mode = 1 -filters = PackedStringArray("*.res", "*.tres") - -[connection signal="value_changed" from="HBoxContainer/VBoxContainer/HBoxContainer/Weight" to="." method="_on_weight_value_changed"] -[connection signal="value_changed" from="HBoxContainer/VBoxContainer/HBoxContainer2/Worth" to="." method="_on_worth_value_changed"] -[connection signal="toggled" from="HBoxContainer/VBoxContainer/HBoxContainer3/Stack" to="." method="_on_stack_toggled"] -[connection signal="text_submitted" from="HBoxContainer/VBoxContainer/BaseID" to="." method="_on_base_id_text_submitted"] -[connection signal="pressed" from="HBoxContainer/VBoxContainer/VBoxContainer/AddTag" to="." method="_on_add_tag_pressed"] -[connection signal="pressed" from="HBoxContainer/VBoxContainer2/HBoxContainer/AddComponent" to="." method="_on_add_component_pressed"] -[connection signal="pressed" from="HBoxContainer/VBoxContainer2/HBoxContainer/OpenComponent" to="." method="_on_open_component_pressed"] -[connection signal="prefab_set" from="HBoxContainer/VBoxContainer3/Prefab" to="." method="_on_prefab_prefab_set"] -[connection signal="prefab_set" from="HBoxContainer/VBoxContainer3/HeldItem" to="." method="_on_held_item_prefab_set"] diff --git a/tools/item_instance_inspector.gd b/tools/item_instance_inspector.gd deleted file mode 100644 index 42cd486..0000000 --- a/tools/item_instance_inspector.gd +++ /dev/null @@ -1,11 +0,0 @@ -@tool -extends PanelContainer - - -var editing: ItemInstance - - -func edit(e:ItemInstance) -> void: - editing = e - $VBoxContainer/HBoxContainer.edit(e) - $VBoxContainer/ItemDataInspector.edit(e.item_data) diff --git a/tools/item_instance_inspector.tscn b/tools/item_instance_inspector.tscn deleted file mode 100644 index 88ea226..0000000 --- a/tools/item_instance_inspector.tscn +++ /dev/null @@ -1,23 +0,0 @@ -[gd_scene load_steps=4 format=3 uid="uid://c5jgisju3kt32"] - -[ext_resource type="PackedScene" uid="uid://ljmha04rl7i3" path="res://addons/skelerealms/tools/instance_inspector_widget.tscn" id="1_hastx"] -[ext_resource type="PackedScene" uid="uid://dfxye68yntvkf" path="res://addons/skelerealms/tools/item_data_inspector.tscn" id="1_j4l7y"] -[ext_resource type="Script" path="res://addons/skelerealms/tools/item_instance_inspector.gd" id="1_ywurn"] - -[node name="ItemInstanceInspector" type="PanelContainer"] -anchors_preset = 15 -anchor_right = 1.0 -anchor_bottom = 1.0 -grow_horizontal = 2 -grow_vertical = 2 -script = ExtResource("1_ywurn") - -[node name="VBoxContainer" type="VBoxContainer" parent="."] -layout_mode = 2 - -[node name="HBoxContainer" parent="VBoxContainer" instance=ExtResource("1_hastx")] -layout_mode = 2 - -[node name="ItemDataInspector" parent="VBoxContainer" instance=ExtResource("1_j4l7y")] -layout_mode = 2 -size_flags_vertical = 3 diff --git a/tools/loot_table_inspector.gd b/tools/loot_table_inspector.gd deleted file mode 100644 index 31749bb..0000000 --- a/tools/loot_table_inspector.gd +++ /dev/null @@ -1,113 +0,0 @@ -@tool -extends PanelContainer - - -@onready var back_button:Button = $HBoxContainer/VBoxContainer/Controls/Back -@onready var class_list:OptionButton = $HBoxContainer/VBoxContainer/Controls/ClassSelector -@onready var item_list:ItemList = $HBoxContainer/VBoxContainer/ScrollContainer/ItemList -@onready var inspector_root:Control = %InspectionRoot -@onready var fd:FileDialog = $FileDialog -var source_stack:Array = [] -var table_stack:Array = [] -var working_table:Array[SKLootTableItem]: - get: - return table_stack.back() -var path_to_class:Dictionary = {} -@export var class_to_editor:Dictionary = {} - -signal table_updated(src:Object, tbl:Array[SKLootTableItem]) - - -func _ready() -> void: - fd.files_selected.connect(func(fs:PackedStringArray) -> void: - for s:String in fs: - var r:SKLootTableItem = load(s) - if r: - _add_item(r) - _update_source() - ) - _update_class_list() - - -func edit(lt:SKLootTableItem) -> void: - var n:Control = class_to_editor[path_to_class[lt.get_script().resource_path]].instantiate() - inspector_root.add_child(n) - n.edit(lt, self) - - -func inspect(source:Object, table:SKLootTable) -> void: - if not source == null: - source_stack.push_back(source) - _update_back_button() - table_stack.push_back(table.items.duplicate()) - render(table_stack.back()) - - -func render(table:Array[SKLootTableItem]) -> void: - item_list.clear() - for i:SKLootTableItem in table: - _add_item(i) - - -func _add_item(sk:SKLootTableItem) -> void: - var i:int = item_list.add_item(path_to_class[sk.get_script().resource_path]) - item_list.set_item_metadata(i, sk) - - -func _update_back_button() -> void: - back_button.disabled = source_stack.is_empty() - - -func _update_class_list() -> void: - class_list.clear() - var inherited:Array = SKToolPlugin.find_classes_that_inherit(&"SKLootTableItem") - for d:Dictionary in inherited: - class_list.add_item(d.class) - class_list.set_item_metadata(class_list.item_count - 1, d.path) - path_to_class[d.path] = d.class - - -func _update_source() -> void: - print(source_stack.back()) - print(working_table) - table_updated.emit(source_stack.back(), table_stack.back()) - - -func _on_back_pressed() -> void: - source_stack.pop_back() - table_stack.pop_back() - render(working_table) - _clear_inpector() - - -func _clear_inpector() -> void: - for c:Node in inspector_root.get_children(): - c.queue_free() - - -func _on_add_pressed() -> void: - var n:SKLootTableItem = load(class_list.get_item_metadata(class_list.selected)).new() - _add_item(n) - table_stack.back().push_back(n) - _update_source() - - -func _on_load_pressed() -> void: - fd.popup_centered() - - -func _on_item_list_item_selected(index: int) -> void: - var data:SKLootTableItem = item_list.get_item_metadata(index) - _clear_inpector() - edit(data) - - -func _on_remove_pressed() -> void: - if not item_list.is_anything_selected(): - return - var i:int = item_list.get_selected_items()[0] - var x:SKLootTableItem = item_list.get_item_metadata(i) - item_list.remove_item(i) - table_stack.back().erase(x) - _clear_inpector() - _update_source() diff --git a/tools/loot_table_inspector.tscn b/tools/loot_table_inspector.tscn deleted file mode 100644 index 92e7b9e..0000000 --- a/tools/loot_table_inspector.tscn +++ /dev/null @@ -1,99 +0,0 @@ -[gd_scene load_steps=7 format=3 uid="uid://cqdiw8t17nqug"] - -[ext_resource type="Script" path="res://addons/skelerealms/tools/loot_table_inspector.gd" id="1_wwf4a"] -[ext_resource type="PackedScene" uid="uid://w5vyxa3v0660" path="res://addons/skelerealms/tools/loot_table_inspectors/lt_item_inspector.tscn" id="2_w3sdv"] -[ext_resource type="PackedScene" uid="uid://cqvn04wmesv7a" path="res://addons/skelerealms/tools/loot_table_inspectors/ltx_of_items_inspector.tscn" id="3_tjcwo"] -[ext_resource type="PackedScene" uid="uid://dech7jypxhhh6" path="res://addons/skelerealms/tools/loot_table_inspectors/lt_currency_inspector.tscn" id="4_esh2e"] -[ext_resource type="PackedScene" uid="uid://dwd7mgv6ghhhw" path="res://addons/skelerealms/tools/loot_table_inspectors/lt_itemchance_inspector.tscn" id="5_cr1n4"] -[ext_resource type="PackedScene" uid="uid://b2dv48krtiqnn" path="res://addons/skelerealms/tools/loot_table_inspectors/lt_item_on_condition_expression.tscn" id="6_x8pi3"] - -[node name="LootTableInspector" type="PanelContainer"] -anchors_preset = 15 -anchor_right = 1.0 -anchor_bottom = 1.0 -grow_horizontal = 2 -grow_vertical = 2 -script = ExtResource("1_wwf4a") -class_to_editor = { -"SKLTCurrency": ExtResource("4_esh2e"), -"SKLTItem": ExtResource("2_w3sdv"), -"SKLTItemChance": ExtResource("5_cr1n4"), -"SKLTOnCondition": ExtResource("6_x8pi3"), -"SKLTXOfItem": ExtResource("3_tjcwo") -} - -[node name="HBoxContainer" type="HBoxContainer" parent="."] -layout_mode = 2 - -[node name="VBoxContainer" type="VBoxContainer" parent="HBoxContainer"] -layout_mode = 2 -size_flags_horizontal = 3 - -[node name="Controls" type="HFlowContainer" parent="HBoxContainer/VBoxContainer"] -layout_mode = 2 - -[node name="Back" type="Button" parent="HBoxContainer/VBoxContainer/Controls"] -layout_mode = 2 -text = "< - Back" - -[node name="ClassSelector" type="OptionButton" parent="HBoxContainer/VBoxContainer/Controls"] -layout_mode = 2 -size_flags_horizontal = 3 -item_count = 6 -selected = 0 -popup/item_0/text = "SKLTCurrency" -popup/item_0/id = 0 -popup/item_1/text = "SKLTItem" -popup/item_1/id = 1 -popup/item_2/text = "SKLTItemChance" -popup/item_2/id = 2 -popup/item_3/text = "SKLTItemEntry" -popup/item_3/id = 3 -popup/item_4/text = "SKLTOnCondition" -popup/item_4/id = 4 -popup/item_5/text = "SKLTXOfItem" -popup/item_5/id = 5 - -[node name="Add" type="Button" parent="HBoxContainer/VBoxContainer/Controls"] -layout_mode = 2 -text = "Add" - -[node name="Load" type="Button" parent="HBoxContainer/VBoxContainer/Controls"] -layout_mode = 2 -text = "Add from File" - -[node name="Remove" type="Button" parent="HBoxContainer/VBoxContainer/Controls"] -layout_mode = 2 -text = "Remove Selected" - -[node name="HSeparator" type="HSeparator" parent="HBoxContainer/VBoxContainer"] -layout_mode = 2 - -[node name="ScrollContainer" type="ScrollContainer" parent="HBoxContainer/VBoxContainer"] -layout_mode = 2 -size_flags_horizontal = 3 -size_flags_vertical = 3 - -[node name="ItemList" type="ItemList" parent="HBoxContainer/VBoxContainer/ScrollContainer"] -layout_mode = 2 -size_flags_horizontal = 3 -size_flags_vertical = 3 -allow_reselect = true - -[node name="InspectionRoot" type="PanelContainer" parent="HBoxContainer"] -unique_name_in_owner = true -layout_mode = 2 -size_flags_horizontal = 3 -size_flags_stretch_ratio = 3.33 - -[node name="FileDialog" type="FileDialog" parent="."] -title = "Open File(s)" -ok_button_text = "Open" -file_mode = 1 -filters = PackedStringArray("*.res", "*.tres") - -[connection signal="pressed" from="HBoxContainer/VBoxContainer/Controls/Back" to="." method="_on_back_pressed"] -[connection signal="pressed" from="HBoxContainer/VBoxContainer/Controls/Add" to="." method="_on_add_pressed"] -[connection signal="pressed" from="HBoxContainer/VBoxContainer/Controls/Load" to="." method="_on_load_pressed"] -[connection signal="pressed" from="HBoxContainer/VBoxContainer/Controls/Remove" to="." method="_on_remove_pressed"] -[connection signal="item_selected" from="HBoxContainer/VBoxContainer/ScrollContainer/ItemList" to="." method="_on_item_list_item_selected"] diff --git a/tools/loot_table_inspectors/lt_currency_inspector.gd b/tools/loot_table_inspectors/lt_currency_inspector.gd deleted file mode 100644 index a38b4f0..0000000 --- a/tools/loot_table_inspectors/lt_currency_inspector.gd +++ /dev/null @@ -1,26 +0,0 @@ -@tool -extends PanelContainer - - -var editing:SKLTCurrency -var inspector:Control - - -func edit(e:SKLTCurrency, i:Control) -> void: - editing = e - inspector = i - $VBoxContainer/LineEdit.text = String(e.currency) - $VBoxContainer/HBoxContainer/Min.value = e.amount_min - $VBoxContainer/HBoxContainer2/Max.value = e.amount_max - - -func _on_line_edit_text_submitted(new_text: String) -> void: - editing.currency = StringName(new_text) - - -func _on_min_value_changed(value: float) -> void: - editing.amount_min = roundi(value) - - -func _on_max_value_changed(value: float) -> void: - editing.amount_max = roundi(value) diff --git a/tools/loot_table_inspectors/lt_currency_inspector.tscn b/tools/loot_table_inspectors/lt_currency_inspector.tscn deleted file mode 100644 index d65b64f..0000000 --- a/tools/loot_table_inspectors/lt_currency_inspector.tscn +++ /dev/null @@ -1,43 +0,0 @@ -[gd_scene load_steps=2 format=3 uid="uid://dech7jypxhhh6"] - -[ext_resource type="Script" path="res://addons/skelerealms/tools/loot_table_inspectors/lt_currency_inspector.gd" id="1_xwl45"] - -[node name="LTCurrencyInspector" type="PanelContainer"] -offset_right = 40.0 -offset_bottom = 40.0 -script = ExtResource("1_xwl45") - -[node name="VBoxContainer" type="VBoxContainer" parent="."] -layout_mode = 2 - -[node name="Label" type="Label" parent="VBoxContainer"] -layout_mode = 2 -text = "Loot Table Currency" - -[node name="LineEdit" type="LineEdit" parent="VBoxContainer"] -layout_mode = 2 -placeholder_text = "Currency" - -[node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer"] -layout_mode = 2 - -[node name="Label" type="Label" parent="VBoxContainer/HBoxContainer"] -layout_mode = 2 -text = "Min amount" - -[node name="Min" type="SpinBox" parent="VBoxContainer/HBoxContainer"] -layout_mode = 2 - -[node name="HBoxContainer2" type="HBoxContainer" parent="VBoxContainer"] -layout_mode = 2 - -[node name="Label" type="Label" parent="VBoxContainer/HBoxContainer2"] -layout_mode = 2 -text = "Max amount" - -[node name="Max" type="SpinBox" parent="VBoxContainer/HBoxContainer2"] -layout_mode = 2 - -[connection signal="text_submitted" from="VBoxContainer/LineEdit" to="." method="_on_line_edit_text_submitted"] -[connection signal="value_changed" from="VBoxContainer/HBoxContainer/Min" to="." method="_on_min_value_changed"] -[connection signal="value_changed" from="VBoxContainer/HBoxContainer2/Max" to="." method="_on_max_value_changed"] diff --git a/tools/loot_table_inspectors/lt_item_inspector.gd b/tools/loot_table_inspectors/lt_item_inspector.gd deleted file mode 100644 index e561e11..0000000 --- a/tools/loot_table_inspectors/lt_item_inspector.gd +++ /dev/null @@ -1,30 +0,0 @@ -@tool -extends PanelContainer - - -@onready var fd:FileDialog = $FileDialog -var editing:SKLTItem -var inspector:Control - - -func edit(e:SKLTItem, i:Control) -> void: - editing = e - inspector = i - $VBoxContainer/Path.text = e.resource_path - fd.file_selected.connect(func(p:String) -> void: - var n:ItemData = load(p) - if n: - editing.data = n - ) - - -func _on_open_pressed() -> void: - fd.popup_centered() - - -func _on_create_pressed() -> void: - editing.data = ItemData.new() - - -func _on_button_pressed() -> void: - SKToolPlugin.instance._open_window(editing.item) diff --git a/tools/loot_table_inspectors/lt_item_inspector.tscn b/tools/loot_table_inspectors/lt_item_inspector.tscn deleted file mode 100644 index 93c7e5e..0000000 --- a/tools/loot_table_inspectors/lt_item_inspector.tscn +++ /dev/null @@ -1,36 +0,0 @@ -[gd_scene load_steps=2 format=3 uid="uid://w5vyxa3v0660"] - -[ext_resource type="Script" path="res://addons/skelerealms/tools/loot_table_inspectors/lt_item_inspector.gd" id="1_5j1j8"] - -[node name="Control" type="PanelContainer"] -offset_right = 167.0 -offset_bottom = 128.0 -script = ExtResource("1_5j1j8") - -[node name="VBoxContainer" type="VBoxContainer" parent="."] -layout_mode = 2 - -[node name="Label" type="Label" parent="VBoxContainer"] -layout_mode = 2 -text = "Loot Table Item" - -[node name="Open" type="Button" parent="VBoxContainer"] -layout_mode = 2 -text = "Open from file" - -[node name="Create" type="Button" parent="VBoxContainer"] -layout_mode = 2 -text = "Create" - -[node name="Button" type="Button" parent="VBoxContainer"] -layout_mode = 2 -text = "Open Item Inspector" - -[node name="Path" type="Label" parent="VBoxContainer"] -layout_mode = 2 - -[node name="FileDialog" type="FileDialog" parent="."] - -[connection signal="pressed" from="VBoxContainer/Open" to="." method="_on_open_pressed"] -[connection signal="pressed" from="VBoxContainer/Create" to="." method="_on_create_pressed"] -[connection signal="pressed" from="VBoxContainer/Button" to="." method="_on_button_pressed"] diff --git a/tools/loot_table_inspectors/lt_item_on_condition_expression.gd b/tools/loot_table_inspectors/lt_item_on_condition_expression.gd deleted file mode 100644 index 26c145c..0000000 --- a/tools/loot_table_inspectors/lt_item_on_condition_expression.gd +++ /dev/null @@ -1,25 +0,0 @@ -@tool -extends PanelContainer - - -@onready var ce:CodeEdit = $VBoxContainer/CodeEdit -var editing:SKLTOnCondition -var inspector:Control - - -func edit(e:SKLTOnCondition, i:Control) -> void: - editing = e - inspector = i - ce.text = e.condition - inspector.table_updated.connect(func(o:Object, tbl:Array[SKLootTableItem]) -> void: - if o == self: - editing.items.items = tbl - ) - - -func _on_code_edit_text_changed() -> void: - editing.condition = ce.text - - -func _on_inspect_pressed() -> void: - inspector.inspect(self, editing.items) diff --git a/tools/loot_table_inspectors/lt_item_on_condition_expression.tscn b/tools/loot_table_inspectors/lt_item_on_condition_expression.tscn deleted file mode 100644 index 269c833..0000000 --- a/tools/loot_table_inspectors/lt_item_on_condition_expression.tscn +++ /dev/null @@ -1,27 +0,0 @@ -[gd_scene load_steps=2 format=3 uid="uid://b2dv48krtiqnn"] - -[ext_resource type="Script" path="res://addons/skelerealms/tools/loot_table_inspectors/lt_item_on_condition_expression.gd" id="1_mnxyk"] - -[node name="LTItemOnConditionExpression" type="PanelContainer"] -offset_right = 40.0 -offset_bottom = 40.0 -script = ExtResource("1_mnxyk") - -[node name="VBoxContainer" type="VBoxContainer" parent="."] -layout_mode = 2 - -[node name="Label" type="Label" parent="VBoxContainer"] -layout_mode = 2 -text = "Loot Table Condition" - -[node name="CodeEdit" type="CodeEdit" parent="VBoxContainer"] -custom_minimum_size = Vector2(0, 128) -layout_mode = 2 -placeholder_text = "Script..." - -[node name="Inspect" type="Button" parent="VBoxContainer"] -layout_mode = 2 -text = "Inspect Items" - -[connection signal="text_changed" from="VBoxContainer/CodeEdit" to="." method="_on_code_edit_text_changed"] -[connection signal="pressed" from="VBoxContainer/Inspect" to="." method="_on_inspect_pressed"] diff --git a/tools/loot_table_inspectors/lt_itemchance_inspector.gd b/tools/loot_table_inspectors/lt_itemchance_inspector.gd deleted file mode 100644 index c6b0a60..0000000 --- a/tools/loot_table_inspectors/lt_itemchance_inspector.gd +++ /dev/null @@ -1,34 +0,0 @@ -@tool -extends PanelContainer - - -@onready var fd:FileDialog = $FileDialog -var editing:SKLTItemChance -var inspector:Control - - -func edit(e:SKLTItemChance, i:Control) -> void: - editing = e - inspector = i - $VBoxContainer/Path.text = e.resource_path - fd.file_selected.connect(func(p:String) -> void: - var n:ItemData = load(p) - if n: - editing.item = n - ) - - -func _on_open_pressed() -> void: - fd.show() - - -func _on_create_pressed() -> void: - editing.item = ItemData.new() - - -func _on_inspect_pressed() -> void: - SKToolPlugin.instance._open_window(editing.item) - - -func _on_h_slider_value_changed(value: float) -> void: - editing.chance = value diff --git a/tools/loot_table_inspectors/lt_itemchance_inspector.tscn b/tools/loot_table_inspectors/lt_itemchance_inspector.tscn deleted file mode 100644 index 6393a5d..0000000 --- a/tools/loot_table_inspectors/lt_itemchance_inspector.tscn +++ /dev/null @@ -1,54 +0,0 @@ -[gd_scene load_steps=2 format=3 uid="uid://dwd7mgv6ghhhw"] - -[ext_resource type="Script" path="res://addons/skelerealms/tools/loot_table_inspectors/lt_itemchance_inspector.gd" id="1_ra8f0"] - -[node name="LTItemChanceInspector" type="PanelContainer"] -offset_right = 40.0 -offset_bottom = 40.0 -script = ExtResource("1_ra8f0") - -[node name="VBoxContainer" type="VBoxContainer" parent="."] -layout_mode = 2 - -[node name="Label" type="Label" parent="VBoxContainer"] -layout_mode = 2 -text = "Loot Table Item Chance" - -[node name="Open" type="Button" parent="VBoxContainer"] -layout_mode = 2 -text = "Open from file" - -[node name="Create" type="Button" parent="VBoxContainer"] -layout_mode = 2 -text = "Create" - -[node name="Inspect" type="Button" parent="VBoxContainer"] -layout_mode = 2 -text = "Open Item Inspector" - -[node name="Path" type="Label" parent="VBoxContainer"] -layout_mode = 2 - -[node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer"] -layout_mode = 2 - -[node name="Label" type="Label" parent="VBoxContainer/HBoxContainer"] -layout_mode = 2 -text = "Chance" - -[node name="HSlider" type="HSlider" parent="VBoxContainer/HBoxContainer"] -layout_mode = 2 -size_flags_horizontal = 3 -max_value = 1.0 -step = 0.01 - -[node name="FileDialog" type="FileDialog" parent="."] -title = "Open a File" -ok_button_text = "Open" -file_mode = 0 -filters = PackedStringArray("*.res", "*.tres") - -[connection signal="pressed" from="VBoxContainer/Open" to="." method="_on_open_pressed"] -[connection signal="pressed" from="VBoxContainer/Create" to="." method="_on_create_pressed"] -[connection signal="pressed" from="VBoxContainer/Inspect" to="." method="_on_inspect_pressed"] -[connection signal="value_changed" from="VBoxContainer/HBoxContainer/HSlider" to="." method="_on_h_slider_value_changed"] diff --git a/tools/loot_table_inspectors/ltx_of_items_inspector.gd b/tools/loot_table_inspectors/ltx_of_items_inspector.gd deleted file mode 100644 index f74a859..0000000 --- a/tools/loot_table_inspectors/ltx_of_items_inspector.gd +++ /dev/null @@ -1,29 +0,0 @@ -@tool -extends PanelContainer - - -var editing:SKLTXOfItem -var inspector:Control - - -func edit(e:SKLTXOfItem, i:Control) -> void: - editing = e - inspector = i - $VBoxContainer/HBoxContainer/Min.value = e.x_min - $VBoxContainer/HBoxContainer2/Max.value = e.x_max - inspector.table_updated.connect(func(o:Object, tbl:Array[SKLootTableItem]) -> void: - if o == self: - editing.items.items = tbl - ) - - -func _on_min_value_changed(value: float) -> void: - editing.x_min = roundi(value) - - -func _on_max_value_changed(value: float) -> void: - editing.x_max = roundi(value) - - -func _on_inspect_table_pressed() -> void: - inspector.inspect(self, editing.items) diff --git a/tools/loot_table_inspectors/ltx_of_items_inspector.tscn b/tools/loot_table_inspectors/ltx_of_items_inspector.tscn deleted file mode 100644 index 5961edc..0000000 --- a/tools/loot_table_inspectors/ltx_of_items_inspector.tscn +++ /dev/null @@ -1,43 +0,0 @@ -[gd_scene load_steps=2 format=3 uid="uid://cqvn04wmesv7a"] - -[ext_resource type="Script" path="res://addons/skelerealms/tools/loot_table_inspectors/ltx_of_items_inspector.gd" id="1_lmfk2"] - -[node name="LTXOfItemsInspector" type="PanelContainer"] -script = ExtResource("1_lmfk2") - -[node name="VBoxContainer" type="VBoxContainer" parent="."] -layout_mode = 2 - -[node name="Label" type="Label" parent="VBoxContainer"] -layout_mode = 2 -text = "Loot Table X of Items" - -[node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer"] -layout_mode = 2 - -[node name="Label" type="Label" parent="VBoxContainer/HBoxContainer"] -layout_mode = 2 -text = "Min items" - -[node name="Min" type="SpinBox" parent="VBoxContainer/HBoxContainer"] -layout_mode = 2 -allow_greater = true - -[node name="HBoxContainer2" type="HBoxContainer" parent="VBoxContainer"] -layout_mode = 2 - -[node name="Label" type="Label" parent="VBoxContainer/HBoxContainer2"] -layout_mode = 2 -text = "Max items" - -[node name="Max" type="SpinBox" parent="VBoxContainer/HBoxContainer2"] -layout_mode = 2 -allow_greater = true - -[node name="InspectTable" type="Button" parent="VBoxContainer"] -layout_mode = 2 -text = "Inspect Table" - -[connection signal="value_changed" from="VBoxContainer/HBoxContainer/Min" to="." method="_on_min_value_changed"] -[connection signal="value_changed" from="VBoxContainer/HBoxContainer2/Max" to="." method="_on_max_value_changed"] -[connection signal="pressed" from="VBoxContainer/InspectTable" to="." method="_on_inspect_table_pressed"] diff --git a/tools/npc_data_inspector.gd b/tools/npc_data_inspector.gd deleted file mode 100644 index 824c730..0000000 --- a/tools/npc_data_inspector.gd +++ /dev/null @@ -1,220 +0,0 @@ -@tool -extends Control - - -const RELATIONSHIP_EDITOR = preload("relationship_editor.tscn") -const COVEN_RANK_EDITOR = preload("coven_rank_data_editor.tscn") - -@onready var add_module:Button = $NPC/panels/AIModule/VBoxContainer/New/Add -@onready var load_module_button:Button = $NPC/panels/AIModule/VBoxContainer/New/LoadModule -@onready var load_module_file:FileDialog = $NPC/panels/AIModule/VBoxContainer/New/LoadModule/FileDialog -@onready var module_list:ItemList = $NPC/panels/AIModule/VBoxContainer/ItemList -@onready var mod_class_selection:OptionButton = $NPC/panels/AIModule/VBoxContainer/New/ModuleSelection - -@onready var goap_class_selection:OptionButton = $NPC/panels/GOAPModules/VBoxContainer/New/GoapSelection -@onready var add_goap:Button = $NPC/panels/GOAPModules/VBoxContainer/New/Add -@onready var load_goap_button:Button = $NPC/panels/GOAPModules/VBoxContainer/New/LoadGoap -@onready var load_goap_file:FileDialog = $NPC/panels/GOAPModules/VBoxContainer/New/LoadGoap/FileDialog -@onready var goap_list:ItemList = $NPC/panels/GOAPModules/VBoxContainer/ItemList - -@onready var relationship_list:Control = $NPC/panels/PanelContainer/VBox/Relationships/ScrollContainer/VBoxContainer -@onready var covens_list:Control = $NPC/panels/PanelContainer/VBox/Covens/ScrollContainer/VBoxContainer - -var prefab_path:String: - set(val): - prefab_path = val - $Prefab.set_path_label(val) -var editing_data:NPCData - - -func refresh_class_list() -> void: - mod_class_selection.clear() - var inherited:Array = SKToolPlugin.find_classes_that_inherit(&"AIModule") - for d:Dictionary in inherited: - mod_class_selection.add_item(d.class) - mod_class_selection.set_item_metadata(mod_class_selection.item_count - 1, d.path) - - -func refresh_goap_list() -> void: - goap_class_selection.clear() - var inherited:Array = SKToolPlugin.find_classes_that_inherit(&"GOAPBehavior") - for d:Dictionary in inherited: - goap_class_selection.add_item(d.class) - goap_class_selection.set_item_metadata(goap_class_selection.item_count - 1, d.path) - - -func _ready() -> void: - # Refresh - refresh_class_list() - refresh_goap_list() - $Prefab.clear() - # Modules - load_module_file.files_selected.connect(func(paths:PackedStringArray) -> void: - for path:String in paths: - add_module_to_list(load(path)) - update_ai_modules() - ) - add_module.pressed.connect(func() -> void: - var n:AIModuleGroup = AIModuleGroup.new() - add_module_to_list(n) - update_ai_modules() - ) - load_module_button.pressed.connect(load_module_file.popup_centered.bind()) - # Prefab - $Prefab.prefab_set.connect(func(p:String) -> void: - prefab_path = p - update_prefab() - ) - # GOAP - load_goap_file.files_selected.connect(func(paths:PackedStringArray) -> void: - for path:String in paths: - add_goap_to_list(load(path)) - update_goap_behaviors() - ) - add_goap.pressed.connect(func() -> void: - var n:GOAPBehaviorGroup = GOAPBehaviorGroup.new() - add_goap_to_list(n) - update_goap_behaviors() - ) - load_goap_button.pressed.connect(load_goap_file.popup_centered.bind()) - # Relationship - $NPC/panels/PanelContainer/VBox/Relationships/Button.pressed.connect(func() -> void: - add_relationship(Relationship.new()) - update_relationships() - ) - # Coven Rank Data - $NPC/panels/PanelContainer/VBox/Covens/Button.pressed.connect(func() -> void: - add_coven(CovenRankData.new()) - update_coven_ranks() - ) - - -func edit(o: NPCData) -> void: - await ready - editing_data = o - - if editing_data == null: - editing_data = NPCData.new() - if editing_data.loot_table == null: - editing_data.loot_table = SKLootTable.new() - - %BaseID.text = editing_data.id - for g:GOAPBehaviorGroup in editing_data.goap_behaviors: - add_goap_to_list(g) - for a:AIModuleGroup in editing_data.ai_modules: - add_module_to_list(a) - # Flags - # i hate my life - %Essential.button_pressed = editing_data.essential - %Essential.toggled.connect(func(s:bool) -> void: editing_data.essential = s) - %Ghost.button_pressed = editing_data.ghost - %Ghost.toggled.connect(func(s:bool) -> void: editing_data.ghost = s) - %Invulnerable.button_pressed = editing_data.invulnerable - %Invulnerable.toggled.connect(func(s:bool) -> void: editing_data.invulnerable = s) - %Unique.button_pressed = editing_data.unique - %Unique.toggled.connect(func(s:bool) -> void: editing_data.unique = s) - %StealthMeter.button_pressed = editing_data.affects_stealth_meter - %StealthMeter.toggled.connect(func(s:bool) -> void: editing_data.affects_stealth_meter = s) - %Interactive.button_pressed = editing_data.interactive - %Interactive.toggled.connect(func(s:bool) -> void: editing_data.interactive = s) - # Relationships - %DefaultOpinion.value = editing_data.default_player_opinion - %DefaultOpinion.value_changed.connect(func(v:float)->void: editing_data.default_player_opinion = roundi(v)) - for r:Relationship in editing_data.relationships: - add_relationship(r) - # Covens - for crd:CovenRankData in editing_data.covens: - add_coven(crd) - # Schedule - $Schedule.edit(editing_data.schedule) - # Loot table - $"Loot Table".inspect(self, editing_data.loot_table) - # Prefab - if not editing_data.prefab == null: - $Prefab.set_to_scene(editing_data.prefab.resource_path) - - -func add_module_to_list(mod:AIModuleGroup) -> void: - var s:String = "" - for i:int in range(min(mod.modules.size(), 3)): - s += mod.modules[i].get_type() + ", " - if mod.modules.size() > 3: - s += "..." - - var i:int = module_list.add_item(s) - module_list.set_item_metadata(i, mod) - - -func add_goap_to_list(goap:GOAPBehaviorGroup) -> void: - var s:String = "" - for i:int in range(min(goap.behaviors.size(), 3)): - s += goap.behaviors[i].id + ", " - if goap.behaviors.size() > 3: - s += "..." - var i:int = goap_list.add_item(s) - goap_list.set_item_metadata(i, goap) - - -func add_relationship(r:Relationship) -> void: - var new_relationship:Node = RELATIONSHIP_EDITOR.instantiate() - new_relationship.edit(r) - new_relationship.delete_request.connect(func() -> void: - new_relationship.queue_free() - await new_relationship.tree_exited - update_relationships() - ) - relationship_list.add_child(new_relationship) - - -func add_coven(c:CovenRankData) -> void: - var new_crd:Node = COVEN_RANK_EDITOR.instantiate() - new_crd.edit(c) - new_crd.delete_requested.connect(func() -> void: - new_crd.queue_free() - await new_crd.tree_exited - update_coven_ranks() - ) - covens_list.add_child(new_crd) - - -func update_ai_modules() -> void: - var output:Array[AIModule] = [] - for i:int in range(module_list.item_count): - output.append(module_list.get_item_metadata(i)) - editing_data.modules = output - - -func update_goap_behaviors() -> void: - var output:Array[GOAPBehavior] = [] - for i:int in range(goap_list.item_count): - output.append(goap_list.get_item_metadata(i)) - editing_data.goap_actions = output - - -func update_relationships() -> void: - editing_data.relationships = relationship_list.get_children().map(func(n:Node) -> Relationship: return n.editing) - - -func update_coven_ranks() -> void: - editing_data.covens = covens_list.get_children().map(func(n:Node) -> CovenRankData: return n.editing) - - -func update_prefab() -> void: - if prefab_path == "": - editing_data.prefab = null - else: - editing_data.prefab = load(prefab_path) - - -func _on_base_id_text_submitted(new_text: String) -> void: - editing_data.id = new_text - - -func _on_schedule_update_event_array(arr: Array[ScheduleEvent]) -> void: - editing_data.schedule = arr - - -func _on_loot_table_table_updated(src: Object, tbl: Array[SKLootTableItem]) -> void: - if not src == self: - return - editing_data.loot_table.items = tbl diff --git a/tools/npc_data_inspector.tscn b/tools/npc_data_inspector.tscn deleted file mode 100644 index 3b568e8..0000000 --- a/tools/npc_data_inspector.tscn +++ /dev/null @@ -1,262 +0,0 @@ -[gd_scene load_steps=5 format=3 uid="uid://muytdxx0rc5u"] - -[ext_resource type="Script" path="res://addons/skelerealms/tools/npc_data_inspector.gd" id="1_x05bx"] -[ext_resource type="PackedScene" uid="uid://bsj34wkkfoi3p" path="res://addons/skelerealms/tools/prefab_selector.tscn" id="2_ok2v4"] -[ext_resource type="PackedScene" uid="uid://cqdiw8t17nqug" path="res://addons/skelerealms/tools/loot_table_inspector.tscn" id="3_vmo81"] -[ext_resource type="PackedScene" uid="uid://slj5y768s6qx" path="res://addons/skelerealms/tools/schedule_editor.tscn" id="4_fq8sx"] - -[node name="NPCDataInspector" type="TabContainer"] -anchors_preset = 15 -anchor_right = 1.0 -anchor_bottom = 1.0 -grow_horizontal = 2 -grow_vertical = 2 -script = ExtResource("1_x05bx") - -[node name="NPC" type="PanelContainer" parent="."] -layout_mode = 2 - -[node name="panels" type="HBoxContainer" parent="NPC"] -layout_mode = 2 - -[node name="VBoxContainer" type="VBoxContainer" parent="NPC/panels"] -layout_mode = 2 - -[node name="Flags" type="GridContainer" parent="NPC/panels/VBoxContainer"] -layout_mode = 2 -columns = 2 - -[node name="Essential" type="CheckBox" parent="NPC/panels/VBoxContainer/Flags"] -unique_name_in_owner = true -layout_mode = 2 -button_pressed = true -text = "essential" - -[node name="Ghost" type="CheckBox" parent="NPC/panels/VBoxContainer/Flags"] -unique_name_in_owner = true -layout_mode = 2 -text = "ghost" - -[node name="Invulnerable" type="CheckBox" parent="NPC/panels/VBoxContainer/Flags"] -unique_name_in_owner = true -layout_mode = 2 -text = "invulnerable" - -[node name="Unique" type="CheckBox" parent="NPC/panels/VBoxContainer/Flags"] -unique_name_in_owner = true -layout_mode = 2 -button_pressed = true -text = "unique" - -[node name="StealthMeter" type="CheckBox" parent="NPC/panels/VBoxContainer/Flags"] -unique_name_in_owner = true -layout_mode = 2 -button_pressed = true -text = "affects stealth meter" - -[node name="Interactive" type="CheckBox" parent="NPC/panels/VBoxContainer/Flags"] -unique_name_in_owner = true -layout_mode = 2 -text = "interactive" - -[node name="BaseID" type="LineEdit" parent="NPC/panels/VBoxContainer"] -unique_name_in_owner = true -layout_mode = 2 -placeholder_text = "BaseID" - -[node name="AIModule" type="PanelContainer" parent="NPC/panels"] -layout_mode = 2 -size_flags_horizontal = 3 - -[node name="VBoxContainer" type="VBoxContainer" parent="NPC/panels/AIModule"] -layout_mode = 2 - -[node name="Label" type="Label" parent="NPC/panels/AIModule/VBoxContainer"] -layout_mode = 2 -text = "AI Modules" - -[node name="New" type="HBoxContainer" parent="NPC/panels/AIModule/VBoxContainer"] -layout_mode = 2 - -[node name="ModuleSelection" type="OptionButton" parent="NPC/panels/AIModule/VBoxContainer/New"] -visible = false -layout_mode = 2 -size_flags_horizontal = 3 -item_count = 9 -selected = 0 -popup/item_0/text = "AnimatorMovementModule" -popup/item_0/id = 0 -popup/item_1/text = "CombatModule" -popup/item_1/id = 1 -popup/item_2/text = "DefaultCrimeReportModule" -popup/item_2/id = 2 -popup/item_3/text = "DefaultDamageModule" -popup/item_3/id = 3 -popup/item_4/text = "DefaultInteractResponseModule" -popup/item_4/id = 4 -popup/item_5/text = "DefaultMovementModule" -popup/item_5/id = 5 -popup/item_6/text = "DefaultThreatResponseModule" -popup/item_6/id = 6 -popup/item_7/text = "HandsManagerModule" -popup/item_7/id = 7 -popup/item_8/text = "TalkInteractModule" -popup/item_8/id = 8 - -[node name="LoadModule" type="Button" parent="NPC/panels/AIModule/VBoxContainer/New"] -layout_mode = 2 -text = "Add AI Module Group from disk" - -[node name="FileDialog" type="FileDialog" parent="NPC/panels/AIModule/VBoxContainer/New/LoadModule"] -title = "Open File(s)" -size = Vector2i(312, 159) -ok_button_text = "Open" -file_mode = 1 -filters = PackedStringArray("*.tres", "*.res") - -[node name="Add" type="Button" parent="NPC/panels/AIModule/VBoxContainer/New"] -layout_mode = 2 -text = "Add" - -[node name="ItemList" type="ItemList" parent="NPC/panels/AIModule/VBoxContainer"] -layout_mode = 2 -size_flags_vertical = 3 - -[node name="GOAPModules" type="PanelContainer" parent="NPC/panels"] -layout_mode = 2 -size_flags_horizontal = 3 - -[node name="VBoxContainer" type="VBoxContainer" parent="NPC/panels/GOAPModules"] -layout_mode = 2 - -[node name="Label" type="Label" parent="NPC/panels/GOAPModules/VBoxContainer"] -layout_mode = 2 -text = "GOAP Behaviors" - -[node name="New" type="HBoxContainer" parent="NPC/panels/GOAPModules/VBoxContainer"] -layout_mode = 2 - -[node name="GoapSelection" type="OptionButton" parent="NPC/panels/GOAPModules/VBoxContainer/New"] -visible = false -layout_mode = 2 -size_flags_horizontal = 3 -item_count = 9 -selected = 0 -popup/item_0/text = "AttackBehavior" -popup/item_0/id = 0 -popup/item_1/text = "ChooseCombatTargetBehavior" -popup/item_1/id = 1 -popup/item_2/text = "EnemiesAlreadyDeadBehavior" -popup/item_2/id = 2 -popup/item_3/text = "GOAPIdlePointBehavior" -popup/item_3/id = 3 -popup/item_4/text = "GoToCombatBehavior" -popup/item_4/id = 4 -popup/item_5/text = "HasTargetBehavior" -popup/item_5/id = 5 -popup/item_6/text = "MeleeAttackBehavior" -popup/item_6/id = 6 -popup/item_7/text = "RangedAttackBehavior" -popup/item_7/id = 7 -popup/item_8/text = "SelectAttackBehavior" -popup/item_8/id = 8 - -[node name="LoadGoap" type="Button" parent="NPC/panels/GOAPModules/VBoxContainer/New"] -layout_mode = 2 -text = "Add GOAP Behavior Group from disk" - -[node name="FileDialog" type="FileDialog" parent="NPC/panels/GOAPModules/VBoxContainer/New/LoadGoap"] -title = "Open File(s)" -size = Vector2i(312, 159) -ok_button_text = "Open" -file_mode = 1 -filters = PackedStringArray("*.tres", "*.res") - -[node name="Add" type="Button" parent="NPC/panels/GOAPModules/VBoxContainer/New"] -layout_mode = 2 -text = "Add" - -[node name="ItemList" type="ItemList" parent="NPC/panels/GOAPModules/VBoxContainer"] -layout_mode = 2 -size_flags_vertical = 3 - -[node name="PanelContainer" type="PanelContainer" parent="NPC/panels"] -layout_mode = 2 -size_flags_horizontal = 3 - -[node name="VBox" type="VBoxContainer" parent="NPC/panels/PanelContainer"] -layout_mode = 2 - -[node name="Covens" type="VBoxContainer" parent="NPC/panels/PanelContainer/VBox"] -layout_mode = 2 -size_flags_vertical = 3 - -[node name="Label" type="Label" parent="NPC/panels/PanelContainer/VBox/Covens"] -layout_mode = 2 -text = "Covens" - -[node name="Button" type="Button" parent="NPC/panels/PanelContainer/VBox/Covens"] -layout_mode = 2 -text = "Add Coven Rank" - -[node name="ScrollContainer" type="ScrollContainer" parent="NPC/panels/PanelContainer/VBox/Covens"] -layout_mode = 2 -size_flags_vertical = 3 - -[node name="VBoxContainer" type="VBoxContainer" parent="NPC/panels/PanelContainer/VBox/Covens/ScrollContainer"] -layout_mode = 2 -size_flags_horizontal = 3 -size_flags_vertical = 3 - -[node name="HSeparator" type="HSeparator" parent="NPC/panels/PanelContainer/VBox"] -layout_mode = 2 - -[node name="Relationships" type="VBoxContainer" parent="NPC/panels/PanelContainer/VBox"] -layout_mode = 2 -size_flags_vertical = 3 - -[node name="Label" type="Label" parent="NPC/panels/PanelContainer/VBox/Relationships"] -layout_mode = 2 -text = "Relationships" - -[node name="HBoxContainer" type="HBoxContainer" parent="NPC/panels/PanelContainer/VBox/Relationships"] -layout_mode = 2 - -[node name="Label" type="Label" parent="NPC/panels/PanelContainer/VBox/Relationships/HBoxContainer"] -layout_mode = 2 -text = "Default Player Opinion" - -[node name="DefaultOpinion" type="SpinBox" parent="NPC/panels/PanelContainer/VBox/Relationships/HBoxContainer"] -unique_name_in_owner = true -layout_mode = 2 -min_value = -100.0 -rounded = true - -[node name="Button" type="Button" parent="NPC/panels/PanelContainer/VBox/Relationships"] -layout_mode = 2 -text = "Add Relationship" - -[node name="ScrollContainer" type="ScrollContainer" parent="NPC/panels/PanelContainer/VBox/Relationships"] -layout_mode = 2 -size_flags_vertical = 3 - -[node name="VBoxContainer" type="VBoxContainer" parent="NPC/panels/PanelContainer/VBox/Relationships/ScrollContainer"] -layout_mode = 2 -size_flags_horizontal = 3 -size_flags_vertical = 3 - -[node name="Prefab" parent="." instance=ExtResource("2_ok2v4")] -visible = false -layout_mode = 2 - -[node name="Loot Table" parent="." instance=ExtResource("3_vmo81")] -visible = false -layout_mode = 2 - -[node name="Schedule" parent="." instance=ExtResource("4_fq8sx")] -visible = false -layout_mode = 2 - -[connection signal="text_submitted" from="NPC/panels/VBoxContainer/BaseID" to="." method="_on_base_id_text_submitted"] -[connection signal="table_updated" from="Loot Table" to="." method="_on_loot_table_table_updated"] -[connection signal="update_event_array" from="Schedule" to="." method="_on_schedule_update_event_array"] diff --git a/tools/npc_instance_inspector.gd b/tools/npc_instance_inspector.gd deleted file mode 100644 index 98ca7dc..0000000 --- a/tools/npc_instance_inspector.gd +++ /dev/null @@ -1,11 +0,0 @@ -@tool -extends PanelContainer - - -var editing: NPCInstance - - -func edit(e:NPCInstance) -> void: - editing = e - $VBoxContainer/HBoxContainer.edit(e) - $VBoxContainer/NPCDataInspector.edit(e.npc_data) diff --git a/tools/npc_instance_inspector.tscn b/tools/npc_instance_inspector.tscn deleted file mode 100644 index 5ca2ab8..0000000 --- a/tools/npc_instance_inspector.tscn +++ /dev/null @@ -1,23 +0,0 @@ -[gd_scene load_steps=4 format=3 uid="uid://bmwuob5jkfixt"] - -[ext_resource type="Script" path="res://addons/skelerealms/tools/npc_instance_inspector.gd" id="1_fdfpv"] -[ext_resource type="PackedScene" uid="uid://ljmha04rl7i3" path="res://addons/skelerealms/tools/instance_inspector_widget.tscn" id="1_run1l"] -[ext_resource type="PackedScene" uid="uid://muytdxx0rc5u" path="res://addons/skelerealms/tools/npc_data_inspector.tscn" id="2_gy720"] - -[node name="NPCInstanceInspector" type="PanelContainer"] -anchors_preset = 15 -anchor_right = 1.0 -anchor_bottom = 1.0 -grow_horizontal = 2 -grow_vertical = 2 -script = ExtResource("1_fdfpv") - -[node name="VBoxContainer" type="VBoxContainer" parent="."] -layout_mode = 2 - -[node name="HBoxContainer" parent="VBoxContainer" instance=ExtResource("1_run1l")] -layout_mode = 2 - -[node name="NPCDataInspector" parent="VBoxContainer" instance=ExtResource("2_gy720")] -layout_mode = 2 -size_flags_vertical = 3 diff --git a/scripts/world_objects/point_gizmo.gd b/tools/point_gizmo.gd similarity index 92% rename from scripts/world_objects/point_gizmo.gd rename to tools/point_gizmo.gd index 4307474..644c31f 100644 --- a/scripts/world_objects/point_gizmo.gd +++ b/tools/point_gizmo.gd @@ -1,4 +1,3 @@ -class_name PointGizmo extends EditorNode3DGizmoPlugin @@ -15,7 +14,7 @@ func _init() -> void: func _has_gizmo(for_node_3d) -> bool: match for_node_3d.get_script(): - WorldMarker, NPCSpawnPoint, IdlePoint: + NPCSpawnPoint, IdlePoint: return true _: return false diff --git a/tools/prefab_selector.gd b/tools/prefab_selector.gd deleted file mode 100644 index deffdad..0000000 --- a/tools/prefab_selector.gd +++ /dev/null @@ -1,35 +0,0 @@ -@tool -extends PanelContainer - - -var scene:String - -signal prefab_set(path:String) - - -func _on_prefab_target_prefab_set(path: String) -> void: - prefab_set.emit(path) - - -func _on_prefab_target_scene_set(s: String) -> void: - scene = s - - -func set_path_label(s: String) -> void: - $VBoxContainer/PathLabel.text = s - - -func clear() -> void: - $VBoxContainer/PrefabTarget.texture = null - $VBoxContainer/PrefabTarget.scene = "" - - -func set_to_scene(path:String) -> void: - $VBoxContainer/PrefabTarget.set_thumb(path) - set_path_label(path) - - -func _on_clear_pressed() -> void: - clear() - scene = "" - prefab_set.emit("") diff --git a/tools/prefab_selector.tscn b/tools/prefab_selector.tscn deleted file mode 100644 index 4a89999..0000000 --- a/tools/prefab_selector.tscn +++ /dev/null @@ -1,43 +0,0 @@ -[gd_scene load_steps=4 format=3 uid="uid://bsj34wkkfoi3p"] - -[ext_resource type="Script" path="res://addons/skelerealms/tools/prefab_selector.gd" id="1_b16nu"] -[ext_resource type="Script" path="res://addons/skelerealms/tools/prefab_target.gd" id="1_e411w"] - -[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_jyaw7"] - -[node name="Prefab" type="PanelContainer"] -script = ExtResource("1_b16nu") - -[node name="VBoxContainer" type="VBoxContainer" parent="."] -layout_mode = 2 - -[node name="PathLabel" type="Label" parent="VBoxContainer"] -custom_minimum_size = Vector2(128, 0) -layout_mode = 2 -autowrap_mode = 1 - -[node name="PrefabTarget" type="TextureRect" parent="VBoxContainer"] -custom_minimum_size = Vector2(128, 128) -layout_mode = 2 -size_flags_horizontal = 0 -size_flags_vertical = 0 -stretch_mode = 2 -script = ExtResource("1_e411w") - -[node name="PanelContainer" type="Panel" parent="VBoxContainer/PrefabTarget"] -layout_mode = 1 -anchors_preset = 15 -anchor_right = 1.0 -anchor_bottom = 1.0 -grow_horizontal = 2 -grow_vertical = 2 -size_flags_vertical = 3 -theme_override_styles/panel = SubResource("StyleBoxFlat_jyaw7") - -[node name="Clear" type="Button" parent="VBoxContainer"] -layout_mode = 2 -text = "Clear" - -[connection signal="prefab_set" from="VBoxContainer/PrefabTarget" to="." method="_on_prefab_target_prefab_set"] -[connection signal="scene_set" from="VBoxContainer/PrefabTarget" to="." method="_on_prefab_target_scene_set"] -[connection signal="pressed" from="VBoxContainer/Clear" to="." method="_on_clear_pressed"] diff --git a/tools/prefab_target.gd b/tools/prefab_target.gd deleted file mode 100644 index a558ace..0000000 --- a/tools/prefab_target.gd +++ /dev/null @@ -1,38 +0,0 @@ -@tool -extends TextureRect - - -var scene:String - -signal prefab_set(path:String) -signal scene_set(s: String) - - -func _can_drop_data(_at_position: Vector2, data: Variant) -> bool: - if not data is Dictionary: - return false - if not data.has("files"): - return false - if not data.files.size() == 0: - return false - return data.files.all(func(x:String) -> bool: return x.ends_with(".tscn") or x.ends_with(".res")) - - -func _drop_data(_at_position: Vector2, data: Variant) -> void: - EditorInterface.get_resource_previewer().queue_resource_preview(data.files[0], self, &"get_thumb", null) - - -func set_thumb(path:String) -> void: - EditorInterface.get_resource_previewer().queue_resource_preview(path, self, &"_silent_set_thumb", null) - - -func _silent_set_thumb(path:String, preview:Texture2D, _thumb:Texture2D, _ud:Variant) -> void: - scene = path - texture = preview - - -func get_thumb(path:String, preview:Texture2D, _thumb:Texture2D, _ud:Variant) -> void: - scene = path - scene_set.emit(path) - texture = preview - prefab_set.emit(path) diff --git a/tools/relationship_editor.gd b/tools/relationship_editor.gd deleted file mode 100644 index 0c3e730..0000000 --- a/tools/relationship_editor.gd +++ /dev/null @@ -1,37 +0,0 @@ -@tool -extends PanelContainer - - -var editing:Relationship -@onready var type_dropdown:OptionButton = $HBoxContainer/VBoxContainer/Type/OptionButton - -signal delete_request - - -func _ready() -> void: - type_dropdown.clear() - for t:String in Relationship.RelationshipLevel.keys(): - type_dropdown.add_item(t) - - -func edit(r:Relationship) -> void: - editing = r - $HBoxContainer/VBoxContainer/Other/OtherEdit.text = editing.other_person - $HBoxContainer/VBoxContainer/Role/RoleEdit.text = editing.role - - -func _on_button_pressed() -> void: - delete_request.emit() - - -func _on_other_edit_text_submitted(new_text: String) -> void: - editing.other_person = new_text - - -func _on_role_edit_text_submitted(new_text: String) -> void: - editing.role = new_text - - -func _on_option_button_item_selected(index: int) -> void: - var i:Relationship.RelationshipLevel = Relationship.RelationshipLevel.keys().find(type_dropdown.get_item_text(index)) - editing.level = i diff --git a/tools/relationship_editor.tscn b/tools/relationship_editor.tscn deleted file mode 100644 index d90da37..0000000 --- a/tools/relationship_editor.tscn +++ /dev/null @@ -1,80 +0,0 @@ -[gd_scene load_steps=2 format=3 uid="uid://dk1jvw0c01kwe"] - -[ext_resource type="Script" path="res://addons/skelerealms/tools/relationship_editor.gd" id="1_ihapx"] - -[node name="RelationshipEditor" type="PanelContainer"] -anchors_preset = 15 -anchor_right = 1.0 -anchor_bottom = 1.0 -grow_horizontal = 2 -grow_vertical = 2 -script = ExtResource("1_ihapx") - -[node name="HBoxContainer" type="HBoxContainer" parent="."] -layout_mode = 2 - -[node name="VBoxContainer" type="VBoxContainer" parent="HBoxContainer"] -layout_mode = 2 -size_flags_horizontal = 3 - -[node name="Other" type="HBoxContainer" parent="HBoxContainer/VBoxContainer"] -layout_mode = 2 - -[node name="Label" type="Label" parent="HBoxContainer/VBoxContainer/Other"] -layout_mode = 2 -text = "Other Person" - -[node name="OtherEdit" type="LineEdit" parent="HBoxContainer/VBoxContainer/Other"] -layout_mode = 2 -size_flags_horizontal = 3 - -[node name="Role" type="HBoxContainer" parent="HBoxContainer/VBoxContainer"] -layout_mode = 2 - -[node name="Label" type="Label" parent="HBoxContainer/VBoxContainer/Role"] -layout_mode = 2 -text = "Role" - -[node name="RoleEdit" type="LineEdit" parent="HBoxContainer/VBoxContainer/Role"] -layout_mode = 2 -size_flags_horizontal = 3 - -[node name="Type" type="HBoxContainer" parent="HBoxContainer/VBoxContainer"] -layout_mode = 2 - -[node name="Label" type="Label" parent="HBoxContainer/VBoxContainer/Type"] -layout_mode = 2 -text = "Type" - -[node name="OptionButton" type="OptionButton" parent="HBoxContainer/VBoxContainer/Type"] -layout_mode = 2 -size_flags_horizontal = 3 -item_count = 9 -selected = 0 -popup/item_0/text = "NEMESIS" -popup/item_0/id = 0 -popup/item_1/text = "ENEMY" -popup/item_1/id = 1 -popup/item_2/text = "FOE" -popup/item_2/id = 2 -popup/item_3/text = "RIVAL" -popup/item_3/id = 3 -popup/item_4/text = "ACQUAINTANCE" -popup/item_4/id = 4 -popup/item_5/text = "FRIEND" -popup/item_5/id = 5 -popup/item_6/text = "BFF" -popup/item_6/id = 6 -popup/item_7/text = "ALLY" -popup/item_7/id = 7 -popup/item_8/text = "LOVER" -popup/item_8/id = 8 - -[node name="Button" type="Button" parent="HBoxContainer"] -layout_mode = 2 -text = "Remove" - -[connection signal="text_submitted" from="HBoxContainer/VBoxContainer/Other/OtherEdit" to="." method="_on_other_edit_text_submitted"] -[connection signal="text_submitted" from="HBoxContainer/VBoxContainer/Role/RoleEdit" to="." method="_on_role_edit_text_submitted"] -[connection signal="item_selected" from="HBoxContainer/VBoxContainer/Type/OptionButton" to="." method="_on_option_button_item_selected"] -[connection signal="pressed" from="HBoxContainer/Button" to="." method="_on_button_pressed"] diff --git a/tools/resource_conversion_plugin.gd b/tools/resource_conversion_plugin.gd new file mode 100644 index 0000000..27b69e9 --- /dev/null +++ b/tools/resource_conversion_plugin.gd @@ -0,0 +1,27 @@ +@tool +extends EditorInspectorPlugin + + +func _can_handle(object: Object) -> bool: + return object is InstanceData + + +func _parse_begin(object: Object) -> void: + var id := object as InstanceData + var b := Button.new() + b.text = "Convert to Entity" + b.pressed.connect(_convert.bind(id)) + add_custom_control(b) + + +func _convert(id:InstanceData) -> void: + var ps:PackedScene = id.convert_to_scene() + if not ps: + return + var path := "" + if id.resource_path.ends_with(".tres"): + path = id.resource_path.trim_suffix(".tres") + ".tscn" + ResourceSaver.save(ps, path) + else: + path = id.resource_path.trim_suffix(".res") + ".scn" + ResourceSaver.save(ps, path, ResourceSaver.FLAG_COMPRESS) diff --git a/tools/schedule_editor.gd b/tools/schedule_editor.gd index e76ba91..a9fac88 100644 --- a/tools/schedule_editor.gd +++ b/tools/schedule_editor.gd @@ -24,7 +24,7 @@ func _ready() -> void: timeline_width = timeline.size.x var op:OptionButton = $VBoxContainer/HBoxContainer/OptionButton op.clear() - var inherited:Array = SKToolPlugin.find_classes_that_inherit(&"ScheduleEvent") + var inherited:Array = find_classes_that_inherit(&"ScheduleEvent") for d:Dictionary in inherited: op.add_item(d.class) op.set_item_metadata(op.item_count - 1, d.path) @@ -188,3 +188,8 @@ func add_box(e:ScheduleEvent) -> void: ) %Container.add_child(b) b.get_child(0).edit(e, self) + + +static func find_classes_that_inherit(what:StringName) -> Array: + return ProjectSettings.get_global_class_list()\ + .filter(func(d:Dictionary)->bool: return d.base == what) diff --git a/tools/schedule_editor.tscn b/tools/schedule_editor.tscn index be3ea83..aeeb4a6 100644 --- a/tools/schedule_editor.tscn +++ b/tools/schedule_editor.tscn @@ -16,7 +16,7 @@ layout_mode = 2 mouse_filter = 0 [node name="HBoxContainer" type="HBoxContainer" parent="ScrollContainer"] -custom_minimum_size = Vector2(1920, 0) +custom_minimum_size = Vector2(4096, 0) layout_mode = 2 size_flags_horizontal = 3 size_flags_vertical = 3 diff --git a/tools/schedule_editor_plugin.gd b/tools/schedule_editor_plugin.gd new file mode 100644 index 0000000..77d8a04 --- /dev/null +++ b/tools/schedule_editor_plugin.gd @@ -0,0 +1,18 @@ +@tool +extends EditorInspectorPlugin + + +const ScheduleEditor := preload("res://addons/skelerealms/tools/schedule_editor.tscn") + +signal request_open(events: Array[ScheduleEvent]) + + +func _can_handle(object: Object) -> bool: + return object is Schedule + + +func _parse_begin(object: Object) -> void: + var b := Button.new() + b.text = "Open schedule editor" + b.pressed.connect(func() -> void: request_open.emit((object as Schedule).events)) + add_custom_control(b) diff --git a/tools/sk_tool_plugin.gd b/tools/sk_tool_plugin.gd deleted file mode 100644 index 6f8a289..0000000 --- a/tools/sk_tool_plugin.gd +++ /dev/null @@ -1,87 +0,0 @@ -class_name SKToolPlugin -extends EditorInspectorPlugin - - -const EDITORS:Dictionary = { - &"NpcInstance": preload("npc_instance_inspector.tscn"), - &"ItemInstance": preload("item_instance_inspector.tscn"), - &"NpcData": preload("npc_data_inspector.tscn"), - &"ItemData": preload("item_data_inspector.tscn"), - &"Coven": preload("coven_editor.tscn"), - &"LootTable": preload("loot_table_inspector.tscn") -} -static var instance:SKToolPlugin - - -func _init() -> void: - instance = self - - -func _can_handle(object: Object) -> bool: - return object is InstanceData\ - or object is RefData\ - or object is Coven\ - or object is SKLootTable - - -func _parse_begin(object: Object) -> void: - var b:Button = Button.new() - b.text = "Open Advanced Editor" - b.pressed.connect(_open_window.bind(object)) - add_custom_control(b) - - -func _open_window(object: Object) -> void: - var w:Window = Window.new() - w.position = EditorInterface.get_base_control().size / 2 - (EditorInterface.get_base_control().size / 4) - w.min_size = EditorInterface.get_base_control().size / 2 - if object is NPCInstance: - var n:Node = EDITORS.NpcInstance.instantiate() - w.add_child(n) - n.edit(object) - elif object is NPCData: - var n:Node = EDITORS.NpcData.instantiate() - w.add_child(n) - n.edit(object) - elif object is ItemInstance: - var n:Node = EDITORS.ItemInstance.instantiate() - w.add_child(n) - n.edit(object) - elif object is ItemData: - var n:Node = EDITORS.ItemData.instantiate() - w.add_child(n) - n.edit(object) - elif object is Coven: - var n:Node = EDITORS.Coven.instantiate() - w.add_child(n) - n.edit(object) - elif object is ChestInstance: - #open_loot_inspector((object as ChestInstance).loot_table) - return - elif object is SKLootTable: - open_loot_inspector((object as SKLootTable).items) - return - else: - return - EditorInterface.get_base_control().add_child(w) - w.close_requested.connect(w.queue_free.bind()) - w.show() - - -static func find_classes_that_inherit(what:StringName) -> Array: - return ProjectSettings.get_global_class_list()\ - .filter(func(d:Dictionary)->bool: return d.base == what) - - -func open_loot_inspector(i:Array[SKLootTableItem]) -> void: - var w:Window = Window.new() - w.position = EditorInterface.get_base_control().size / 2 - (EditorInterface.get_base_control().size / 4) - w.min_size = EditorInterface.get_base_control().size / 2 - - var e:Node = EDITORS.LootTable.instantiate() - w.add_child(e) - e.edit(null, i) - - EditorInterface.get_base_control().add_child(w) - w.close_requested.connect(w.queue_free.bind()) - w.show() diff --git a/tools/template_selector.gd b/tools/template_selector.gd new file mode 100644 index 0000000..05addad --- /dev/null +++ b/tools/template_selector.gd @@ -0,0 +1,101 @@ +@tool +extends PanelContainer + + +const FILE_INHERIT = 1 + +@onready var option_button: OptionButton = $VBoxContainer/HBoxContainer/OptionButton +@onready var file_dialog: FileDialog = $FileDialog + +var editing:SKWorldEntity + + +# life will be pain until this gets merged https://github.com/godotengine/godot/pull/90057 + + +# Called when the node enters the scene tree for the first time. +func _ready() -> void: + var templates:PackedStringArray = ProjectSettings.get_setting("skelerealms/entity_archetypes") + option_button.clear() + for t:String in templates: + option_button.add_item(t) + + +func edit(what:SKWorldEntity) -> void: + editing = what + + +func _on_button_pressed() -> void: + file_dialog.popup_centered() + #_create_using_editor() + + +func _grab_uid(path:String) -> String: + return ResourceUID.id_to_text(ResourceLoader.get_resource_uid(path)) + + +func _generate_inherited_scene_id() -> String: + return "1_%s" % SKIDGenerator.generate_id(5).to_lower() + + +func _format_scene(from:String, entity_name:String) -> String: + var id:String = _generate_inherited_scene_id() + var uid:String = ResourceUID.id_to_text(ResourceUID.create_id()) + return """ +[gd_scene load_steps=2 format=3 uid=\"%s\"] + +[ext_resource type=\"PackedScene\" uid=\"%s\" path=\"%s\" id=\"%s\"] + +[node name=\"%s\" instance=ExtResource(\"%s\")] +""" % [ + uid, + _grab_uid(from), + from, + id, + entity_name, + id +] + + +func _on_file_dialog_file_selected(path: String) -> void: + _make_manually(path) + #_create_using_instantiation(path) + + +func _make_manually(path:String) -> void: + var p:String = option_button.get_item_text(option_button.selected) + var contents:String = _format_scene(p, "test_entity") + var fh := FileAccess.open(path, FileAccess.WRITE) + fh.store_string(contents) + fh.close() + EditorInterface.get_resource_filesystem().scan() + EditorInterface.get_resource_filesystem().update_file(path) + EditorInterface.get_resource_previewer().queue_resource_preview(path, self, &"receive_thumbnail", null) + editing.entity = ResourceLoader.load(path) + + +func _create_using_editor() -> void: + EditorInterface.get_file_system_dock().navigate_to_path(option_button.get_item_text(option_button.selected)) + + var popup:PopupMenu = EditorInterface.get_file_system_dock().get_children()\ + .filter(func(n:Node)->bool:return n is PopupMenu)\ + .filter(func(p:PopupMenu) -> bool: return p.item_count > 0)\ + .filter(func(p:PopupMenu) -> bool: return p.get_item_text(0) == "Open Scene")\ + [0] + + if popup: + popup.id_pressed.emit(FILE_INHERIT) + + +func _create_using_instantiation(path:String) -> void: + var p:String = option_button.get_item_text(option_button.selected) + var new_scene:Node = (ResourceLoader.load(p) as PackedScene).instantiate(PackedScene.GEN_EDIT_STATE_MAIN_INHERITED) + new_scene.scene_file_path = p + print(new_scene.scene_file_path) + var new_ps := PackedScene.new() + new_ps.pack(new_scene) + ResourceSaver.save(new_ps, path) + + +func receive_thumbnail(_path:String, _preview:Texture2D, _thumbnail_preview:Texture2D, _userdata:Variant) -> void: + return diff --git a/tools/template_selector.tscn b/tools/template_selector.tscn new file mode 100644 index 0000000..5f6d23b --- /dev/null +++ b/tools/template_selector.tscn @@ -0,0 +1,40 @@ +[gd_scene load_steps=2 format=3 uid="uid://b0c0y6wqyykt4"] + +[ext_resource type="Script" path="res://addons/skelerealms/tools/template_selector.gd" id="1_1j7a1"] + +[node name="PanelContainer" type="PanelContainer"] +offset_right = 40.0 +offset_bottom = 40.0 +script = ExtResource("1_1j7a1") + +[node name="VBoxContainer" type="VBoxContainer" parent="."] +layout_mode = 2 + +[node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer"] +layout_mode = 2 + +[node name="Label" type="Label" parent="VBoxContainer/HBoxContainer"] +layout_mode = 2 +size_flags_horizontal = 3 +text = "Template:" + +[node name="OptionButton" type="OptionButton" parent="VBoxContainer/HBoxContainer"] +layout_mode = 2 +item_count = 3 +selected = 0 +popup/item_0/text = "res://assets/iod_npc_generic_template.tscn" +popup/item_0/id = 0 +popup/item_1/text = "res://addons/skelerealms/npc_entity_template.tscn" +popup/item_1/id = 1 +popup/item_2/text = "res://addons/skelerealms/item_entity_template.tscn" +popup/item_2/id = 2 + +[node name="Button" type="Button" parent="VBoxContainer"] +layout_mode = 2 +text = "Create" + +[node name="FileDialog" type="FileDialog" parent="."] +filters = PackedStringArray("*.scn, *.tscn") + +[connection signal="pressed" from="VBoxContainer/Button" to="." method="_on_button_pressed"] +[connection signal="file_selected" from="FileDialog" to="." method="_on_file_dialog_file_selected"] diff --git a/tools/world_entity_plugin.gd b/tools/world_entity_plugin.gd new file mode 100644 index 0000000..d88c368 --- /dev/null +++ b/tools/world_entity_plugin.gd @@ -0,0 +1,23 @@ +extends EditorInspectorPlugin + + +const TemplateSelector = preload("res://addons/skelerealms/tools/template_selector.tscn") + + +func _can_handle(object: Object) -> bool: + return object is SKWorldEntity + + +func _parse_begin(object: Object) -> void: + var b := Button.new() + b.text = "Sync position with entity" + b.pressed.connect(object._sync.bind()) + + var t := TemplateSelector.instantiate() + t.edit(object) + + var vbox := VBoxContainer.new() + vbox.add_child(b) + vbox.add_child(t) + + add_custom_control(vbox) diff --git a/world_marker_entity_template.tscn b/world_marker_entity_template.tscn new file mode 100644 index 0000000..83c9b55 --- /dev/null +++ b/world_marker_entity_template.tscn @@ -0,0 +1,10 @@ +[gd_scene load_steps=3 format=3 uid="uid://bymqtmaatptgp"] + +[ext_resource type="Script" path="res://addons/skelerealms/scripts/entities/entity.gd" id="1_rnn1t"] +[ext_resource type="Script" path="res://addons/skelerealms/scripts/components/marker_component.gd" id="2_vcn76"] + +[node name="SKEntity" type="Node"] +script = ExtResource("1_rnn1t") + +[node name="MarkerComponent" type="Node" parent="."] +script = ExtResource("2_vcn76")