liborng is now its own repository, as it always should have been.
This commit is contained in:
Binary file not shown.
@@ -0,0 +1,10 @@
|
||||
env = SConscript("godot-cpp/SConstruct")
|
||||
env.Append(CPPPATH="liborng/")
|
||||
|
||||
src = Glob('liborng/*.cpp')
|
||||
src += Glob('liborng/**/*.cpp')
|
||||
src += Glob('liborng/**/**/*.cpp')
|
||||
|
||||
libpath = '../{}_{}/{}/liborng{}'.format(env['platform'], env['arch'], env['target'], env['SHLIBSUFFIX'])
|
||||
sharedlib = env.SharedLibrary(libpath, src)
|
||||
Default(sharedlib)
|
||||
Submodule
+1
Submodule src/godot-cpp added at 4bc6e67d51
@@ -0,0 +1,4 @@
|
||||
[ViewState]
|
||||
Mode=
|
||||
Vid=
|
||||
FolderType=Generic
|
||||
@@ -0,0 +1,104 @@
|
||||
/*
|
||||
* ©2023 Batty Bovine Productions, LLC. All Rights Reserved.
|
||||
*
|
||||
* If this source code makes it to the public internet, this software can be
|
||||
* considered to be protected by the MIT licence. Have fun with it.
|
||||
*/
|
||||
|
||||
#include "save_file_data.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include <godot_cpp/classes/project_settings.hpp>
|
||||
#include <godot_cpp/variant/utility_functions.hpp>
|
||||
using namespace godot;
|
||||
|
||||
|
||||
void SaveFileData::init()
|
||||
{
|
||||
this->level = ProjectSettings::get_singleton()->get_setting_with_override(FIRST_LEVEL_SETTING);
|
||||
}
|
||||
|
||||
Dictionary SaveFileData::serialize() const
|
||||
{
|
||||
Dictionary dictionary;
|
||||
|
||||
dictionary[KEY_SAVEFILE_LEVEL] = this->level;
|
||||
dictionary[KEY_SAVEFILE_CHECKPOINT] = this->checkpoint;
|
||||
dictionary[KEY_SAVEFILE_DIFFICULTY] = this->difficulty;
|
||||
|
||||
return dictionary;
|
||||
}
|
||||
|
||||
void SaveFileData::deserialize(const Dictionary &dictionary)
|
||||
{
|
||||
this->level = dictionary[KEY_SAVEFILE_LEVEL];
|
||||
this->checkpoint = dictionary[KEY_SAVEFILE_CHECKPOINT];
|
||||
this->difficulty = SaveFileData::DifficultySetting((uint8_t)dictionary[KEY_SAVEFILE_DIFFICULTY]);
|
||||
}
|
||||
|
||||
|
||||
Dictionary SaveHeaderData::serialize() const
|
||||
{
|
||||
Dictionary dictionary;
|
||||
|
||||
dictionary[KEY_HEADER_THUMBNAIL] = this->thumbnail;
|
||||
dictionary[KEY_HEADER_FILENAME] = this->filename;
|
||||
dictionary[KEY_HEADER_DIFFICULTY] = this->difficulty;
|
||||
dictionary[KEY_HEADER_PLAYTIME] = this->playtime;
|
||||
dictionary[KEY_HEADER_LASTSAVEDATE] = this->last_save_date;
|
||||
|
||||
dictionary[KEY_HEADER_LEVEL] = this->level;
|
||||
dictionary[KEY_HEADER_CHECKPOINT] = this->checkpoint;
|
||||
|
||||
return dictionary;
|
||||
}
|
||||
|
||||
void SaveHeaderData::deserialize(const Dictionary &dictionary)
|
||||
{
|
||||
this->thumbnail = dictionary[KEY_HEADER_THUMBNAIL];
|
||||
this->filename = dictionary[KEY_HEADER_FILENAME];
|
||||
this->difficulty = SaveFileData::DifficultySetting((uint8_t)dictionary[KEY_HEADER_DIFFICULTY]);
|
||||
this->playtime = dictionary[KEY_HEADER_PLAYTIME];
|
||||
this->last_save_date = dictionary[KEY_HEADER_LASTSAVEDATE];
|
||||
|
||||
this->level = dictionary[KEY_HEADER_LEVEL];
|
||||
this->checkpoint = dictionary[KEY_HEADER_CHECKPOINT];
|
||||
}
|
||||
|
||||
|
||||
void SaveFileIndex::update_header(const uint8_t slot, const SaveFileData &save_data)
|
||||
{
|
||||
this->headers[slot] = std::make_unique<SaveHeaderData>();
|
||||
this->headers[slot]->level = save_data.level;
|
||||
this->headers[slot]->checkpoint = save_data.checkpoint;
|
||||
this->headers[slot]->difficulty = save_data.difficulty;
|
||||
}
|
||||
|
||||
void SaveFileIndex::clear_slot(const uint8_t slot)
|
||||
{
|
||||
this->headers[slot] = std::make_unique<SaveHeaderData>();
|
||||
}
|
||||
|
||||
Dictionary SaveFileIndex::serialize() const
|
||||
{
|
||||
Dictionary dictionary;
|
||||
|
||||
for (const std::pair<const uint8_t, std::unique_ptr<SaveHeaderData>> &header : this->headers)
|
||||
{
|
||||
dictionary[header.first] = header.second->serialize();
|
||||
}
|
||||
|
||||
return dictionary;
|
||||
}
|
||||
|
||||
void SaveFileIndex::deserialize(const Dictionary &dictionary)
|
||||
{
|
||||
const Array &keys = dictionary.keys();
|
||||
for (uint8_t i = 0; i < keys.size(); i++)
|
||||
{
|
||||
const uint8_t &key = keys[i];
|
||||
this->headers[key] = std::make_unique<SaveHeaderData>();
|
||||
this->headers[key]->deserialize(dictionary[key]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
/*
|
||||
* ©2023 Batty Bovine Productions, LLC. All Rights Reserved.
|
||||
*
|
||||
* If this source code makes it to the public internet, this software can be
|
||||
* considered to be protected by the MIT licence. Have fun with it.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "save_file_data_keys.h"
|
||||
#include "nodes/player_spawn.h"
|
||||
|
||||
#include <map>
|
||||
#include <memory>
|
||||
|
||||
#include <godot_cpp/classes/object.hpp>
|
||||
#include <godot_cpp/classes/packed_scene.hpp>
|
||||
using namespace godot;
|
||||
|
||||
#define FIRST_LEVEL_SETTING "orange_cat/levels/starter_levels/new_save_file_scene"
|
||||
|
||||
class SaveManager;
|
||||
class SaveFileIndex;
|
||||
|
||||
|
||||
class SaveFileSerializerInterface
|
||||
{
|
||||
public:
|
||||
virtual Dictionary serialize() const = 0;
|
||||
virtual void deserialize(const Dictionary &dictionary) = 0;
|
||||
};
|
||||
|
||||
|
||||
class SaveFileData : public Object, public SaveFileSerializerInterface
|
||||
{
|
||||
GDCLASS(SaveFileData, Object);
|
||||
|
||||
friend class SaveManager;
|
||||
friend class SaveFileIndex;
|
||||
|
||||
public:
|
||||
enum DifficultySetting
|
||||
{
|
||||
VERY_EASY,
|
||||
EASY,
|
||||
NORMAL,
|
||||
HARD,
|
||||
VERY_HARD,
|
||||
|
||||
MAX
|
||||
};
|
||||
inline static const char *difficulty_names[DifficultySetting::MAX] = { "Very Easy", "Easy", "Normal", "Hard", "Very Hard" };
|
||||
|
||||
void init();
|
||||
|
||||
virtual Dictionary serialize() const override;
|
||||
virtual void deserialize(const Dictionary &dictionary) override;
|
||||
|
||||
protected:
|
||||
StringName level;
|
||||
StringName checkpoint = PLAYER_ORIGIN_TAG;
|
||||
SaveFileData::DifficultySetting difficulty = SaveFileData::DifficultySetting::NORMAL;
|
||||
|
||||
// Godot boilerplate below
|
||||
protected:
|
||||
static void _bind_methods()
|
||||
{
|
||||
BIND_ENUM_CONSTANT(VERY_EASY);
|
||||
BIND_ENUM_CONSTANT(EASY);
|
||||
BIND_ENUM_CONSTANT(NORMAL);
|
||||
BIND_ENUM_CONSTANT(HARD);
|
||||
BIND_ENUM_CONSTANT(VERY_HARD);
|
||||
}
|
||||
};
|
||||
VARIANT_ENUM_CAST(SaveFileData::DifficultySetting);
|
||||
|
||||
|
||||
class SaveHeaderData : public SaveFileSerializerInterface
|
||||
{
|
||||
friend class SaveFileIndex;
|
||||
|
||||
public:
|
||||
virtual Dictionary serialize() const override;
|
||||
virtual void deserialize(const Dictionary &dictionary) override;
|
||||
|
||||
bool is_empty() const { return this->filename.is_empty(); }
|
||||
|
||||
protected:
|
||||
Ref<Resource> thumbnail;
|
||||
StringName filename;
|
||||
SaveFileData::DifficultySetting difficulty = SaveFileData::DifficultySetting::NORMAL;
|
||||
String playtime;
|
||||
String last_save_date;
|
||||
|
||||
StringName level;
|
||||
StringName checkpoint;
|
||||
};
|
||||
|
||||
class SaveFileIndex : public Object, public SaveFileSerializerInterface
|
||||
{
|
||||
friend class SaveManager;
|
||||
|
||||
public:
|
||||
void update_header(const uint8_t slot, const SaveFileData &save_data);
|
||||
void clear_slot(const uint8_t slot);
|
||||
|
||||
virtual Dictionary serialize() const override;
|
||||
virtual void deserialize(const Dictionary &dictionary) override;
|
||||
|
||||
protected:
|
||||
std::map<const uint8_t, std::unique_ptr<SaveHeaderData>> headers;
|
||||
|
||||
// Godot boilerplate below
|
||||
protected:
|
||||
static void _bind_methods() {}
|
||||
};
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* ©2023 Batty Bovine Productions, LLC. All Rights Reserved.
|
||||
*
|
||||
* If this source code makes it to the public internet, this software can be
|
||||
* considered to be protected by the MIT licence. Have fun with it.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
// SaveHeaderData
|
||||
#ifdef DEBUG_ENABLED
|
||||
#define KEY_HEADER_THUMBNAIL "Thumbnail"
|
||||
#define KEY_HEADER_FILENAME "Filename"
|
||||
#define KEY_HEADER_DIFFICULTY "Difficulty"
|
||||
#define KEY_HEADER_PLAYTIME "Play Time"
|
||||
#define KEY_HEADER_LASTSAVEDATE "Last Save Date"
|
||||
|
||||
#define KEY_HEADER_LEVEL "Level"
|
||||
#define KEY_HEADER_CHECKPOINT "Checkpoint"
|
||||
#else
|
||||
#define KEY_HEADER_THUMBNAIL "🖼️"
|
||||
#define KEY_HEADER_FILENAME "📄"
|
||||
#define KEY_HEADER_DIFFICULTY "💪🏼"
|
||||
#define KEY_HEADER_PLAYTIME "🕓"
|
||||
#define KEY_HEADER_LASTSAVEDATE "📅"
|
||||
|
||||
#define KEY_HEADER_LEVEL "🌆"
|
||||
#define KEY_HEADER_CHECKPOINT "📍"
|
||||
#endif // DEBUG_ENABLED
|
||||
|
||||
// SaveFileData
|
||||
#ifdef DEBUG_ENABLED
|
||||
#define KEY_SAVEFILE_LEVEL "Level"
|
||||
#define KEY_SAVEFILE_CHECKPOINT "Checkpoint"
|
||||
#define KEY_SAVEFILE_DIFFICULTY "Difficulty"
|
||||
#else
|
||||
#define KEY_SAVEFILE_LEVEL "🌆"
|
||||
#define KEY_SAVEFILE_CHECKPOINT "📍"
|
||||
#define KEY_SAVEFILE_DIFFICULTY "💪🏼"
|
||||
#endif // DEBUG_ENABLED
|
||||
@@ -0,0 +1,123 @@
|
||||
/*
|
||||
* ©2024 Batty Bovine Productions, LLC. All Rights Reserved.
|
||||
*
|
||||
* If this source code makes it to the public internet, this software can be
|
||||
* considered to be protected by the MIT licence. Have fun with it.
|
||||
*/
|
||||
|
||||
#include "nodes/controller_menu.h"
|
||||
|
||||
#include <godot_cpp/classes/engine.hpp>
|
||||
#include <godot_cpp/classes/project_settings.hpp>
|
||||
#include <godot_cpp/classes/property_tweener.hpp>
|
||||
#include <godot_cpp/classes/scene_tree.hpp>
|
||||
#include <godot_cpp/variant/utility_functions.hpp>
|
||||
using namespace godot;
|
||||
|
||||
|
||||
void ControllerMenu::_enter_tree()
|
||||
{
|
||||
#ifdef DEBUG_ENABLED
|
||||
if(!Engine::get_singleton()->is_editor_hint())
|
||||
{
|
||||
#endif
|
||||
this->set_visible(false);
|
||||
this->set_modulate(Color(1.0f, 1.0f, 1.0f, 0.0f));
|
||||
#ifdef DEBUG_ENABLED
|
||||
}
|
||||
#endif
|
||||
this->_find_buttons_recursive(const_cast<ControllerMenu*>(this), this->buttons);
|
||||
|
||||
// Buttons should default to not being focusable
|
||||
for (Button *button : this->buttons)
|
||||
{
|
||||
button->set_focus_mode(Control::FocusMode::FOCUS_NONE);
|
||||
}
|
||||
|
||||
if (!this->primary_focus_button)
|
||||
{
|
||||
this->primary_focus_button = this->buttons.front();
|
||||
}
|
||||
|
||||
if (!this->back_button)
|
||||
{
|
||||
this->back_button = this->buttons.back();
|
||||
}
|
||||
|
||||
// We currently expect at least one button
|
||||
DEV_ASSERT(this->primary_focus_button);
|
||||
DEV_ASSERT(this->back_button);
|
||||
}
|
||||
|
||||
void ControllerMenu::_input(const Ref<InputEvent> &event)
|
||||
{
|
||||
if (event->is_action_type() && this->back_button->get_focus_mode() != Control::FocusMode::FOCUS_NONE)
|
||||
{
|
||||
StringName back_action = ProjectSettings::get_singleton()->get_setting("orange_cat/controller_menu/back_action", "ui_cancel");
|
||||
const bool action_state = this->back_button->get_action_mode() == Button::ACTION_MODE_BUTTON_RELEASE ? event->is_action_released(back_action) : event->is_action_pressed(back_action);
|
||||
if (action_state)
|
||||
{
|
||||
if (this->back_button->has_focus())
|
||||
{
|
||||
this->back_button->emit_signal("pressed");
|
||||
}
|
||||
else
|
||||
{
|
||||
this->back_button->grab_focus();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void ControllerMenu::_activate()
|
||||
{
|
||||
this->set_visible(true);
|
||||
this->set_modulate(Color(1.0f, 1.0f, 1.0f, 0.0f));
|
||||
this->create_tween()->tween_property(this, "modulate", Color(1.0f, 1.0f, 1.0f, 1.0f), 0.25f)->connect("finished", callable_mp(this, &ControllerMenu::_finish_activating));
|
||||
}
|
||||
void ControllerMenu::_finish_activating()
|
||||
{
|
||||
for (Button *button : this->buttons)
|
||||
{
|
||||
button->set_focus_mode(Control::FocusMode::FOCUS_ALL);
|
||||
}
|
||||
this->primary_focus_button->grab_focus();
|
||||
this->emit_signal("activated");
|
||||
}
|
||||
|
||||
void ControllerMenu::_deactivate()
|
||||
{
|
||||
for (Button *button : this->buttons)
|
||||
{
|
||||
if (button->has_focus())
|
||||
{
|
||||
this->primary_focus_button = button;
|
||||
}
|
||||
button->set_focus_mode(Control::FocusMode::FOCUS_NONE);
|
||||
}
|
||||
this->set_modulate(Color(1.0f, 1.0f, 1.0f, 1.0f));
|
||||
this->create_tween()->tween_property(this, "modulate", Color(1.0f, 1.0f, 1.0f, 0.0f), 0.25f)->connect("finished", callable_mp(this, &ControllerMenu::_finish_deactivating));
|
||||
}
|
||||
void ControllerMenu::_finish_deactivating()
|
||||
{
|
||||
this->set_visible(false);
|
||||
this->emit_signal("deactivated");
|
||||
}
|
||||
|
||||
|
||||
void ControllerMenu::_find_buttons_recursive(Control *control, std::vector<Button*> &buttons) const
|
||||
{
|
||||
const TypedArray<Node> &children = control->get_children();
|
||||
for (int i = 0; i < children.size(); i++)
|
||||
{
|
||||
if (Control *child = cast_to<Control>(children[i]))
|
||||
{
|
||||
if (child->is_class("Button"))
|
||||
{
|
||||
buttons.push_back(cast_to<Button>(child));
|
||||
}
|
||||
this->_find_buttons_recursive(child, buttons);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
* ©2024 Batty Bovine Productions, LLC. All Rights Reserved.
|
||||
*
|
||||
* If this source code makes it to the public internet, this software can be
|
||||
* considered to be protected by the MIT licence. Have fun with it.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "orng_macros.h"
|
||||
|
||||
#include <godot_cpp/classes/button.hpp>
|
||||
#include <godot_cpp/classes/input_event.hpp>
|
||||
using namespace godot;
|
||||
|
||||
|
||||
class ControllerMenu : public Control
|
||||
{
|
||||
friend class MenuStack;
|
||||
|
||||
GDCLASS(ControllerMenu, Control);
|
||||
|
||||
public:
|
||||
virtual void _enter_tree() override;
|
||||
virtual void _input(const Ref<InputEvent> &event) override;
|
||||
|
||||
void set_primary_focus_button(Button *button) { this->primary_focus_button = button; }
|
||||
Button *get_primary_focus_button() const { return this->primary_focus_button; }
|
||||
|
||||
void set_back_button(Button *button) { this->back_button = button; }
|
||||
Button *get_back_button() const { return this->back_button; }
|
||||
|
||||
protected:
|
||||
Button *primary_focus_button = nullptr;
|
||||
Button *back_button = nullptr;
|
||||
|
||||
private:
|
||||
void _activate();
|
||||
void _finish_activating();
|
||||
|
||||
void _deactivate();
|
||||
void _finish_deactivating();
|
||||
|
||||
void _find_buttons_recursive(Control *control, std::vector<Button*> &buttons) const;
|
||||
std::vector<Button*> buttons;
|
||||
|
||||
// Godot boilerplate below
|
||||
protected:
|
||||
static void _bind_methods()
|
||||
{
|
||||
{ // Functions
|
||||
ADD_GETTER_SETTER_HINTED(ControllerMenu, primary_focus_button, Variant::OBJECT, PROPERTY_HINT_NODE_TYPE, "Button");
|
||||
ADD_GETTER_SETTER_HINTED(ControllerMenu, back_button, Variant::OBJECT, PROPERTY_HINT_NODE_TYPE, "Button");
|
||||
|
||||
BIND_METHOD(ControllerMenu, _finish_activating);
|
||||
BIND_METHOD(ControllerMenu, _finish_deactivating);
|
||||
}
|
||||
|
||||
{ // Signals
|
||||
ADD_SIGNAL(MethodInfo("activated"));
|
||||
ADD_SIGNAL(MethodInfo("deactivated"));
|
||||
|
||||
ADD_SIGNAL(MethodInfo("push_new_menu", PropertyInfo(Variant::OBJECT, "menu", PROPERTY_HINT_RESOURCE_TYPE, "PackedScene")));
|
||||
ADD_SIGNAL(MethodInfo("pop_menu"));
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,13 @@
|
||||
/*
|
||||
* ©2024 Batty Bovine Productions, LLC. All Rights Reserved.
|
||||
*
|
||||
* If this source code makes it to the public internet, this software can be
|
||||
* considered to be protected by the MIT licence. Have fun with it.
|
||||
*/
|
||||
|
||||
#include "nodes/controller_message.h"
|
||||
|
||||
using namespace godot;
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* ©2024 Batty Bovine Productions, LLC. All Rights Reserved.
|
||||
*
|
||||
* If this source code makes it to the public internet, this software can be
|
||||
* considered to be protected by the MIT licence. Have fun with it.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "nodes/controller_menu.h"
|
||||
using namespace godot;
|
||||
|
||||
|
||||
class ControllerMessage : public ControllerMenu
|
||||
{
|
||||
GDCLASS(ControllerMessage, ControllerMenu);
|
||||
|
||||
public:
|
||||
void set_message_text_two_buttons(StringName message, StringName confirm, StringName cancel) { this->message_text = message; this->confirm_text = confirm; this->cancel_text = cancel; }
|
||||
void set_message_text_one_button(StringName message, StringName cancel) { this->message_text = message; this->cancel_text = cancel; this->button_count = ControllerMessage::ButtonCount::ONE_BUTTON; }
|
||||
|
||||
enum ButtonCount {
|
||||
ONE_BUTTON,
|
||||
TWO_BUTTONS
|
||||
};
|
||||
|
||||
protected:
|
||||
StringName message_text;
|
||||
StringName confirm_text;
|
||||
StringName cancel_text;
|
||||
|
||||
ControllerMessage::ButtonCount button_count = ControllerMessage::ButtonCount::TWO_BUTTONS;
|
||||
|
||||
// Godot boilerplate below
|
||||
protected:
|
||||
static void _bind_methods()
|
||||
{
|
||||
ClassDB::bind_method(D_METHOD("set_message_text", "message", "confirm", "cancel"), &ControllerMessage::set_message_text_one_button);
|
||||
ClassDB::bind_method(D_METHOD("set_message_text", "message", "cancel"), &ControllerMessage::set_message_text_two_buttons);
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* ©2023 Batty Bovine Productions, LLC. All Rights Reserved.
|
||||
*
|
||||
* If this source code makes it to the public internet, this software can be
|
||||
* considered to be protected by the MIT licence. Have fun with it.
|
||||
*/
|
||||
|
||||
#include "hide_in_game.h"
|
||||
|
||||
#include <godot_cpp/classes/engine.hpp>
|
||||
#include <godot_cpp/variant/utility_functions.hpp>
|
||||
using namespace godot;
|
||||
|
||||
|
||||
void HideInGame::_enter_tree()
|
||||
{
|
||||
#ifdef DEBUG_ENABLED
|
||||
if (!Engine::get_singleton()->is_editor_hint())
|
||||
{
|
||||
#endif
|
||||
this->queue_free();
|
||||
return;
|
||||
#ifdef DEBUG_ENABLED
|
||||
}
|
||||
#endif
|
||||
Node3D::_enter_tree();
|
||||
}
|
||||
|
||||
void HideInGame::_ready()
|
||||
{
|
||||
if (!Engine::get_singleton()->is_editor_hint())
|
||||
{
|
||||
return;
|
||||
}
|
||||
Node3D::_ready();
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* ©2023 Batty Bovine Productions, LLC. All Rights Reserved.
|
||||
*
|
||||
* If this source code makes it to the public internet, this software can be
|
||||
* considered to be protected by the MIT licence. Have fun with it.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <godot_cpp/classes/node3d.hpp>
|
||||
using namespace godot;
|
||||
|
||||
|
||||
class HideInGame : public Node3D
|
||||
{
|
||||
GDCLASS(HideInGame, Node3D);
|
||||
|
||||
public:
|
||||
virtual void _enter_tree() override;
|
||||
virtual void _ready() override;
|
||||
|
||||
// Godot boilerplate below
|
||||
protected:
|
||||
static void _bind_methods(){}
|
||||
};
|
||||
@@ -0,0 +1,108 @@
|
||||
/*
|
||||
* ©2023 Batty Bovine Productions, LLC. All Rights Reserved.
|
||||
*
|
||||
* If this source code makes it to the public internet, this software can be
|
||||
* considered to be protected by the MIT licence. Have fun with it.
|
||||
*/
|
||||
|
||||
#include "level_scene.h"
|
||||
|
||||
#include "nodes/player_spawn.h"
|
||||
#include "singletons/save_manager.h"
|
||||
|
||||
#include <godot_cpp/classes/engine.hpp>
|
||||
#include <godot_cpp/classes/scene_tree.hpp>
|
||||
#include <godot_cpp/classes/window.hpp>
|
||||
#include <godot_cpp/variant/utility_functions.hpp>
|
||||
|
||||
using namespace godot;
|
||||
|
||||
|
||||
void LevelScene::_ready()
|
||||
{
|
||||
#ifdef DEBUG_ENABLED
|
||||
if(Engine::get_singleton()->is_editor_hint()) { return; }
|
||||
#endif // DEBUG_ENABLED
|
||||
|
||||
// We defer this call until the end of the setup frame, since we'll need
|
||||
// SaveManager to have been registered as a global singleton before we can
|
||||
// find the correct player spawn. This should not be necessary any time
|
||||
// after the first scene has loaded and the singleton has been registered,
|
||||
// but for safety purposes this still makes sense right now.
|
||||
this->call_deferred("_scene_ready");
|
||||
}
|
||||
void LevelScene::_scene_ready()
|
||||
{
|
||||
// We should probably assert that there is a valid GameClasses file first
|
||||
// thing, since that's vital for initialising LevelScenes.
|
||||
#ifdef DEBUG_ENABLED
|
||||
if (!this->game_classes.is_valid())
|
||||
{
|
||||
UtilityFunctions::push_error("No GameClasses resource attached to level.");
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
this->level_death_plane_node = cast_to<Area3D>(this->get_node_or_null(this->level_death_plane));
|
||||
if (this->level_death_plane_node)
|
||||
{
|
||||
this->level_death_plane_node->set_monitoring(true);
|
||||
|
||||
this->level_death_plane_node->set_gravity_space_override_mode(Area3D::SpaceOverride::SPACE_OVERRIDE_DISABLED);
|
||||
this->level_death_plane_node->set_linear_damp_space_override_mode(Area3D::SpaceOverride::SPACE_OVERRIDE_DISABLED);
|
||||
this->level_death_plane_node->set_angular_damp_space_override_mode(Area3D::SpaceOverride::SPACE_OVERRIDE_DISABLED);
|
||||
|
||||
this->level_death_plane_node->connect("body_entered", callable_mp(this, &LevelScene::_body_fell_out_of_world));
|
||||
}
|
||||
|
||||
SpawnPointMap spawn_points = this->_find_player_spawns();
|
||||
const StringName ¤t_checkpoint = SaveManager::get_singleton()->get_current_checkpoint();
|
||||
|
||||
const PlayerOrigin *spawn_point = spawn_points[current_checkpoint];
|
||||
#ifdef DEBUG_ENABLED
|
||||
if (!spawn_point)
|
||||
{
|
||||
UtilityFunctions::push_error("Could not find a spawn point named ", current_checkpoint, "; defaulting to origin...");
|
||||
spawn_point = spawn_points[PLAYER_ORIGIN_TAG];
|
||||
DEV_ASSERT(spawn_point);
|
||||
}
|
||||
#endif
|
||||
|
||||
// Attempt to spawn the player scene at this spawn point.
|
||||
spawn_point->spawn_player(this, this->game_classes->get_player_scene());
|
||||
}
|
||||
|
||||
|
||||
SpawnPointMap LevelScene::_find_player_spawns() const
|
||||
{
|
||||
SpawnPointMap spawn_points;
|
||||
this->_find_player_spawns_recursive(this, spawn_points);
|
||||
return spawn_points;
|
||||
}
|
||||
|
||||
void LevelScene::_find_player_spawns_recursive(const Node *node, SpawnPointMap &spawn_points) const
|
||||
{
|
||||
if (const PlayerOrigin *potential_spawn = cast_to<PlayerOrigin>(node))
|
||||
{
|
||||
spawn_points.emplace(potential_spawn->get_tag(), potential_spawn);
|
||||
}
|
||||
|
||||
const TypedArray<Node> &children = node->get_children();
|
||||
for (int i = 0; i < children.size(); i++)
|
||||
{
|
||||
this->_find_player_spawns_recursive(cast_to<Node>(children[i]), spawn_points);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void LevelScene::_body_fell_out_of_world(Node3D *body)
|
||||
{
|
||||
if (body->has_method("on_fell_out_of_world"))
|
||||
{
|
||||
body->call("on_fell_out_of_world");
|
||||
}
|
||||
else
|
||||
{
|
||||
body->queue_free();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
* ©2023 Batty Bovine Productions, LLC. All Rights Reserved.
|
||||
*
|
||||
* If this source code makes it to the public internet, this software can be
|
||||
* considered to be protected by the MIT licence. Have fun with it.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "orng_macros.h"
|
||||
|
||||
#include <map>
|
||||
#include <godot_cpp/classes/area3d.hpp>
|
||||
#include <godot_cpp/classes/packed_scene.hpp>
|
||||
#include <godot_cpp/classes/resource.hpp>
|
||||
using namespace godot;
|
||||
|
||||
typedef std::map<const class StringName, const class PlayerOrigin*> SpawnPointMap;
|
||||
|
||||
|
||||
class GameClasses : public Resource
|
||||
{
|
||||
GDCLASS(GameClasses, Resource);
|
||||
|
||||
public:
|
||||
void set_player_scene(Ref<PackedScene> s) { this->player_scene = s; }
|
||||
Ref<PackedScene> get_player_scene() const { return this->player_scene; }
|
||||
|
||||
protected:
|
||||
Ref<PackedScene> player_scene;
|
||||
|
||||
// Godot boilerplate below
|
||||
protected:
|
||||
static void _bind_methods()
|
||||
{
|
||||
ADD_GETTER_SETTER_HINTED(GameClasses, player_scene, Variant::OBJECT, PROPERTY_HINT_RESOURCE_TYPE, "PackedScene");
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
class LevelScene : public Node3D
|
||||
{
|
||||
GDCLASS(LevelScene, Node3D);
|
||||
|
||||
public:
|
||||
virtual void _ready() override;
|
||||
|
||||
protected:
|
||||
void set_game_classes(Ref<GameClasses> c) { this->game_classes = c; }
|
||||
Ref<GameClasses> get_game_classes() const { return this->game_classes; }
|
||||
|
||||
NodePath level_death_plane;
|
||||
void set_level_death_plane(NodePath ldp) { this->level_death_plane = ldp; }
|
||||
NodePath get_level_death_plane() const { return this->level_death_plane; }
|
||||
|
||||
Ref<GameClasses> game_classes;
|
||||
|
||||
private:
|
||||
virtual void _scene_ready();
|
||||
|
||||
virtual void _body_fell_out_of_world(Node3D *body);
|
||||
|
||||
virtual SpawnPointMap _find_player_spawns() const;
|
||||
void _find_player_spawns_recursive(const Node *node, SpawnPointMap &spawn_points) const;
|
||||
|
||||
Area3D *level_death_plane_node = nullptr;
|
||||
|
||||
// Godot boilerplate below
|
||||
protected:
|
||||
static void _bind_methods()
|
||||
{
|
||||
BIND_METHOD(LevelScene, _scene_ready);
|
||||
|
||||
ADD_GETTER_SETTER_HINTED(LevelScene, game_classes, Variant::OBJECT, PROPERTY_HINT_RESOURCE_TYPE, "GameClasses");
|
||||
ADD_GETTER_SETTER_HINTED(LevelScene, level_death_plane, Variant::NODE_PATH, PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Area3D");
|
||||
|
||||
BIND_METHOD(LevelScene, _body_fell_out_of_world);
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,96 @@
|
||||
/*
|
||||
* ©2024 Batty Bovine Productions, LLC. All Rights Reserved.
|
||||
*
|
||||
* If this source code makes it to the public internet, this software can be
|
||||
* considered to be protected by the MIT licence. Have fun with it.
|
||||
*/
|
||||
|
||||
#include "nodes/menu_stack.h"
|
||||
|
||||
#include <godot_cpp/variant/utility_functions.hpp>
|
||||
using namespace godot;
|
||||
|
||||
|
||||
void MenuStack::push_menu(Ref<PackedScene> menu)
|
||||
{
|
||||
// Make sure we're not pushing a menu while one is actively being transitioned.
|
||||
DEV_ASSERT(!this->transition_to_menu);
|
||||
|
||||
if (ControllerMenu *menu_node = cast_to<ControllerMenu>(menu->instantiate()))
|
||||
{
|
||||
this->transition_to_menu = menu_node;
|
||||
this->add_child(this->transition_to_menu);
|
||||
this->menu_stack.push(this->transition_to_menu);
|
||||
|
||||
this->transition_to_menu->connect("pop_menu", callable_mp(this, &MenuStack::pop_menu), Object::ConnectFlags::CONNECT_ONE_SHOT);
|
||||
this->transition_to_menu->connect("push_new_menu", callable_mp(this, &MenuStack::push_menu));
|
||||
|
||||
if (this->active_menu)
|
||||
{
|
||||
this->active_menu->connect("deactivated", callable_mp(this, &MenuStack::old_menu_deactivated), Object::ConnectFlags::CONNECT_ONE_SHOT);
|
||||
this->active_menu->_deactivate();
|
||||
}
|
||||
else
|
||||
{
|
||||
this->start_menu_activation();
|
||||
}
|
||||
}
|
||||
#ifdef DEBUG_ENABLED
|
||||
else
|
||||
{
|
||||
UtilityFunctions::push_error("Invalid menu pushed to stack.");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
void MenuStack::old_menu_deactivated()
|
||||
{
|
||||
this->finish_menu_deactivation();
|
||||
this->start_menu_activation();
|
||||
}
|
||||
void MenuStack::pushed_menu_activated()
|
||||
{
|
||||
this->active_menu = this->transition_to_menu;
|
||||
this->transition_to_menu = nullptr;
|
||||
}
|
||||
|
||||
void MenuStack::pop_menu()
|
||||
{
|
||||
this->menu_stack.pop();
|
||||
this->active_menu->connect("deactivated", callable_mp(this, &MenuStack::pushed_menu_deactivated), Object::ConnectFlags::CONNECT_ONE_SHOT);
|
||||
this->active_menu->_deactivate();
|
||||
}
|
||||
void MenuStack::pushed_menu_deactivated()
|
||||
{
|
||||
this->remove_child(this->active_menu);
|
||||
this->active_menu->queue_free();
|
||||
this->finish_menu_deactivation();
|
||||
|
||||
if (this->transition_to_menu = this->menu_stack.top())
|
||||
{
|
||||
this->start_menu_activation();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void MenuStack::push_message_one_button(StringName message, StringName cancel_text, Callable cancel_callback)
|
||||
{
|
||||
if (this->message = cast_to<ControllerMessage>(this->message_scene->instantiate()))
|
||||
{
|
||||
this->message->set_message_text_one_button(message, cancel_text);
|
||||
this->start_message_activation();
|
||||
}
|
||||
}
|
||||
|
||||
void MenuStack::push_message_two_buttons(StringName message, StringName confirm_text, StringName cancel_text, Callable confirm_callback, Callable cancel_callback)
|
||||
{
|
||||
if (this->message = cast_to<ControllerMessage>(this->message_scene->instantiate()))
|
||||
{
|
||||
this->message->set_message_text_two_buttons(message, confirm_text, cancel_text);
|
||||
this->start_message_activation();
|
||||
}
|
||||
}
|
||||
|
||||
void MenuStack::pop_message()
|
||||
{
|
||||
this->finish_message_deactivation();
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
* ©2024 Batty Bovine Productions, LLC. All Rights Reserved.
|
||||
*
|
||||
* If this source code makes it to the public internet, this software can be
|
||||
* considered to be protected by the MIT licence. Have fun with it.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "orng_macros.h"
|
||||
|
||||
#include <stack>
|
||||
#include "nodes/controller_menu.h"
|
||||
#include "nodes/controller_message.h"
|
||||
|
||||
#include <godot_cpp/classes/control.hpp>
|
||||
#include <godot_cpp/classes/packed_scene.hpp>
|
||||
using namespace godot;
|
||||
|
||||
|
||||
class MenuStack : public Control
|
||||
{
|
||||
GDCLASS(MenuStack, Control);
|
||||
|
||||
public:
|
||||
void push_menu(Ref<PackedScene> menu);
|
||||
void pop_menu();
|
||||
|
||||
void push_message_one_button(StringName message, StringName cancel_text, Callable cancel_callback = Callable());
|
||||
void push_message_two_buttons(StringName message, StringName confirm_text, StringName cancel_text, Callable confirm_callback = Callable(), Callable cancel_callback = Callable());
|
||||
void pop_message();
|
||||
|
||||
void set_message_scene(Ref<PackedScene> scene) { this->message_scene = scene; }
|
||||
Ref<PackedScene> get_message_scene() const { return this->message_scene; }
|
||||
|
||||
ControllerMenu *get_active_menu() const { return this->active_menu; }
|
||||
|
||||
protected:
|
||||
Ref<PackedScene> message_scene;
|
||||
|
||||
private:
|
||||
void pushed_menu_activated();
|
||||
void old_menu_deactivated();
|
||||
void pushed_menu_deactivated();
|
||||
|
||||
void start_menu_activation()
|
||||
{
|
||||
this->transition_to_menu->connect("activated", callable_mp(this, &MenuStack::pushed_menu_activated), Object::ConnectFlags::CONNECT_ONE_SHOT);
|
||||
this->transition_to_menu->_activate();
|
||||
}
|
||||
void finish_menu_deactivation()
|
||||
{
|
||||
this->active_menu = nullptr;
|
||||
}
|
||||
|
||||
void start_message_activation()
|
||||
{
|
||||
this->message->connect("activated", callable_mp(this, &MenuStack::pushed_menu_activated), Object::ConnectFlags::CONNECT_ONE_SHOT);
|
||||
this->message->_activate();
|
||||
}
|
||||
void finish_message_deactivation()
|
||||
{
|
||||
this->message = nullptr;
|
||||
}
|
||||
|
||||
ControllerMenu *active_menu = nullptr;
|
||||
|
||||
ControllerMenu *transition_to_menu = nullptr;
|
||||
|
||||
std::stack<ControllerMenu*> menu_stack;
|
||||
ControllerMessage *message = nullptr;
|
||||
|
||||
// Godot boilerplate below
|
||||
protected:
|
||||
static void _bind_methods()
|
||||
{
|
||||
BIND_METHOD_1PARAM(MenuStack, push_menu, menu);
|
||||
BIND_METHOD(MenuStack, pop_menu);
|
||||
|
||||
BIND_METHOD(MenuStack, get_active_menu);
|
||||
|
||||
BIND_METHOD(MenuStack, pushed_menu_activated);
|
||||
BIND_METHOD(MenuStack, pushed_menu_deactivated);
|
||||
BIND_METHOD(MenuStack, old_menu_deactivated);
|
||||
|
||||
ADD_GETTER_SETTER_HINTED(MenuStack, message_scene, Variant::OBJECT, PROPERTY_HINT_RESOURCE_TYPE, "PackedScene");
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,188 @@
|
||||
/*
|
||||
* ©2023 Batty Bovine Productions, LLC. All Rights Reserved.
|
||||
*
|
||||
* If this source code makes it to the public internet, this software can be
|
||||
* considered to be protected by the MIT licence. Have fun with it.
|
||||
*/
|
||||
|
||||
#include "movement_handler.h"
|
||||
|
||||
#include <godot_cpp/classes/camera3d.hpp>
|
||||
#include <godot_cpp/classes/engine.hpp>
|
||||
#include <godot_cpp/classes/project_settings.hpp>
|
||||
#include <godot_cpp/classes/viewport.hpp>
|
||||
#include <godot_cpp/variant/utility_functions.hpp>
|
||||
using namespace godot;
|
||||
|
||||
|
||||
void MovementHandler::_ready()
|
||||
{
|
||||
#ifdef DEBUG_ENABLED
|
||||
if (!Engine::get_singleton()->is_editor_hint())
|
||||
{
|
||||
#endif // DEBUG_ENABLED
|
||||
|
||||
// If we haven't had a state chart assigned, try to find it as a direct child.
|
||||
this->state_chart_node = this->get_node_or_null(this->state_chart);
|
||||
if (!this->state_chart_node)
|
||||
{
|
||||
const TypedArray<Node> &children = this->get_children();
|
||||
for(int i = 0; i < children.size(); i++)
|
||||
{
|
||||
Node *child = cast_to<Node>(children[i]);
|
||||
if (child->is_class("StateChart"))
|
||||
{
|
||||
this->state_chart_node = child;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!this->state_chart_node)
|
||||
{
|
||||
UtilityFunctions::push_error("No state chart assigned, and none could be found.");
|
||||
}
|
||||
}
|
||||
|
||||
this->controlled_body_node = cast_to<CharacterBody3D>(this->get_node_or_null(this->controlled_body));
|
||||
if (!this->controlled_body_node)
|
||||
{
|
||||
this->controlled_body_node = this->cast_to<CharacterBody3D>(this->get_parent());
|
||||
if(!this->controlled_body_node)
|
||||
{
|
||||
UtilityFunctions::push_error("No controlled body assigned, and none could be found.");
|
||||
}
|
||||
}
|
||||
|
||||
const ProjectSettings *settings = ProjectSettings::get_singleton();
|
||||
this->gravity = float(settings->get_setting("physics/3d/default_gravity")) * this->gravity_multiplier;
|
||||
|
||||
#ifdef DEBUG_ENABLED
|
||||
}
|
||||
#endif // DEBUG_ENABLED
|
||||
}
|
||||
|
||||
|
||||
// Walking processing
|
||||
void MovementHandler::_on_walking_state_entered()
|
||||
{
|
||||
this->emit_signal("state_changed", "walk");
|
||||
}
|
||||
|
||||
void MovementHandler::_on_walking_state_physics_processing(const double _delta)
|
||||
{
|
||||
if (!this->controlled_body_node->is_on_floor())
|
||||
{
|
||||
this->state_chart_node->call("send_event", "fall");
|
||||
}
|
||||
|
||||
this->_calculate_lateral_velocity(_delta, ground_acceleration, ground_friction);
|
||||
|
||||
// Emit the new speed as a range between 0.0 and 1.0 for animation purposes
|
||||
this->emit_signal("movement_speed_changed", "walk", this->controlled_body_node->get_velocity().length() / top_speed);
|
||||
}
|
||||
|
||||
|
||||
// Airborne processing
|
||||
void MovementHandler::_on_airborne_state_entered()
|
||||
{
|
||||
this->emit_signal("state_changed", "fall");
|
||||
}
|
||||
|
||||
void MovementHandler::_on_airborne_state_physics_processing(const double _delta)
|
||||
{
|
||||
if (this->controlled_body_node->is_on_floor())
|
||||
{
|
||||
this->state_chart_node->call("send_event", "stop_falling");
|
||||
}
|
||||
|
||||
this->_calculate_lateral_velocity(_delta, air_acceleration, air_friction);
|
||||
}
|
||||
|
||||
|
||||
// Jumping processing
|
||||
void MovementHandler::_on_jumping_state_entered()
|
||||
{
|
||||
Vector3 controlled_body_velocity = this->controlled_body_node->get_velocity();
|
||||
controlled_body_velocity.y = this->jump_force;
|
||||
this->controlled_body_node->set_velocity(controlled_body_velocity);
|
||||
|
||||
this->emit_signal("state_changed", "jump");
|
||||
}
|
||||
|
||||
void MovementHandler::_on_cancel_jump_state_entered()
|
||||
{
|
||||
Vector3 controlled_body_velocity = this->controlled_body_node->get_velocity();
|
||||
if (controlled_body_velocity.y > 0.0)
|
||||
{
|
||||
controlled_body_velocity.y /= this->jump_cancel_force;
|
||||
this->controlled_body_node->set_velocity(controlled_body_velocity);
|
||||
}
|
||||
}
|
||||
|
||||
void MovementHandler::_on_double_jump_state_entered()
|
||||
{
|
||||
Vector3 controlled_body_velocity = this->controlled_body_node->get_velocity();
|
||||
controlled_body_velocity.y = this->double_jump_force;
|
||||
this->controlled_body_node->set_velocity(controlled_body_velocity);
|
||||
|
||||
this->emit_signal("state_changed", "double_jump");
|
||||
}
|
||||
|
||||
|
||||
// Common physics (stateless)
|
||||
void MovementHandler::_physics_process(double _delta)
|
||||
{
|
||||
#ifdef DEBUG_ENABLED
|
||||
if (Engine::get_singleton()->is_editor_hint())
|
||||
{
|
||||
return;
|
||||
}
|
||||
#endif // DEBUG_ENABLED
|
||||
|
||||
if(!this->controlled_body_node)
|
||||
{
|
||||
UtilityFunctions::push_error("No controlled body has been found, so the physics process can't run.");
|
||||
return;
|
||||
}
|
||||
|
||||
Vector3 velocity = this->controlled_body_node->get_velocity();
|
||||
velocity.y -= this->gravity * _delta;
|
||||
this->controlled_body_node->set_velocity(velocity);
|
||||
|
||||
this->controlled_body_node->move_and_slide();
|
||||
}
|
||||
|
||||
void MovementHandler::_calculate_lateral_velocity(double _delta, float _acceleration, float _friction)
|
||||
{
|
||||
Vector3 controlled_body_velocity = this->controlled_body_node->get_velocity();
|
||||
|
||||
const float stored_y_velocity = controlled_body_velocity.y;
|
||||
|
||||
Camera3D *active_camera = this->get_viewport()->get_camera_3d();
|
||||
const Vector3 rotated_movement_vector = this->current_movement_vector.rotated(this->controlled_body_node->get_transform().get_basis()[Vector3::Axis::AXIS_Y].normalized(), active_camera->get_global_rotation().y);
|
||||
|
||||
// If the character is accelerating based on our current joystick input,
|
||||
// lerp with acceleration value. Otherwise, use friction to decelerate.
|
||||
const float lerp_weight = ((rotated_movement_vector.length() * top_speed) > controlled_body_velocity.length()) ? _acceleration : _friction;
|
||||
controlled_body_velocity = controlled_body_velocity.lerp(rotated_movement_vector * (top_speed * rotated_movement_vector.length()), lerp_weight * _delta);
|
||||
|
||||
controlled_body_velocity.y = stored_y_velocity;
|
||||
|
||||
this->controlled_body_node->set_velocity(controlled_body_velocity);
|
||||
}
|
||||
|
||||
|
||||
// Input response functions
|
||||
void MovementHandler::_on_movement_2d(float _delta, Vector2 _vector)
|
||||
{
|
||||
this->current_movement_vector = Vector3(_vector.x, 0.0, _vector.y);
|
||||
}
|
||||
|
||||
void MovementHandler::_on_jump_pressed()
|
||||
{
|
||||
this->state_chart_node->call("send_event", "jump");
|
||||
}
|
||||
|
||||
void MovementHandler::_on_jump_released()
|
||||
{
|
||||
this->state_chart_node->call("send_event", "stop_jumping");
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
/*
|
||||
* ©2023 Batty Bovine Productions, LLC. All Rights Reserved.
|
||||
*
|
||||
* If this source code makes it to the public internet, this software can be
|
||||
* considered to be protected by the MIT licence. Have fun with it.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "orng_macros.h"
|
||||
|
||||
#include <godot_cpp/classes/character_body3d.hpp>
|
||||
#include <godot_cpp/classes/node.hpp>
|
||||
using namespace godot;
|
||||
|
||||
|
||||
class MovementHandler : public Node
|
||||
{
|
||||
GDCLASS(MovementHandler, Node);
|
||||
|
||||
public:
|
||||
virtual void _ready() override;
|
||||
|
||||
virtual void _physics_process(double _delta) override;
|
||||
|
||||
protected:
|
||||
virtual void _on_walking_state_entered();
|
||||
virtual void _on_walking_state_physics_processing(const double _delta);
|
||||
|
||||
virtual void _on_airborne_state_entered();
|
||||
virtual void _on_airborne_state_physics_processing(const double _delta);
|
||||
|
||||
virtual void _on_jumping_state_entered();
|
||||
virtual void _on_cancel_jump_state_entered();
|
||||
virtual void _on_double_jump_state_entered();
|
||||
|
||||
virtual void _on_movement_2d(float _delta, Vector2 _vector);
|
||||
virtual void _on_jump_pressed();
|
||||
virtual void _on_jump_released();
|
||||
|
||||
NodePath controlled_body;
|
||||
void set_controlled_body(NodePath cb) { this->controlled_body = cb; }
|
||||
NodePath get_controlled_body() const { return this->controlled_body; }
|
||||
|
||||
NodePath state_chart;
|
||||
void set_state_chart(NodePath sc) { this->state_chart = sc; }
|
||||
NodePath get_state_chart() const { return this->state_chart; }
|
||||
|
||||
float top_speed = 6.0f;
|
||||
void set_top_speed(float s) { this->top_speed = s; }
|
||||
float get_top_speed() const { return this->top_speed; }
|
||||
|
||||
float ground_acceleration = 10.0f;
|
||||
void set_ground_acceleration(float a) { this->ground_acceleration = a; }
|
||||
float get_ground_acceleration() const { return this->ground_acceleration; }
|
||||
|
||||
float ground_friction = 10.0f;
|
||||
void set_ground_friction(float f) { this->ground_friction = f; }
|
||||
float get_ground_friction() const { return this->ground_friction; }
|
||||
|
||||
float gravity_multiplier = 4.0f;
|
||||
void set_gravity_multiplier(float m) { this->gravity_multiplier = m; }
|
||||
float get_gravity_multiplier() const { return this->gravity_multiplier; }
|
||||
|
||||
float air_acceleration = 5.0f;
|
||||
void set_air_acceleration(float a) { this->air_acceleration = a; }
|
||||
float get_air_acceleration() const { return this->air_acceleration; }
|
||||
|
||||
float air_friction = 5.0f;
|
||||
void set_air_friction(float f) { this->air_friction = f; }
|
||||
float get_air_friction() const { return this->air_friction; }
|
||||
|
||||
float jump_force = 12.0f;
|
||||
void set_jump_force(float f) { this->jump_force = f; }
|
||||
float get_jump_force() const { return this->jump_force; }
|
||||
|
||||
float jump_cancel_force = 2.0f;
|
||||
void set_jump_cancel_force(float c) { this->jump_cancel_force = c; }
|
||||
float get_jump_cancel_force() const { return this->jump_cancel_force; }
|
||||
|
||||
float double_jump_force = 12.0f;
|
||||
void set_double_jump_force(float f) { this->double_jump_force = f; }
|
||||
float get_double_jump_force() const { return this->double_jump_force; }
|
||||
|
||||
Vector3 jump_stretch_size = Vector3(0.8f, 1.2f, 0.8f);
|
||||
void set_jump_stretch_size(Vector3 s) { this->jump_stretch_size = s; }
|
||||
Vector3 get_jump_stretch_size() const { return this->jump_stretch_size; }
|
||||
|
||||
private:
|
||||
void _calculate_lateral_velocity(double _delta, float _acceleration, float _friction);
|
||||
|
||||
CharacterBody3D *controlled_body_node = nullptr;
|
||||
Node3D *camera_boom_node = nullptr;
|
||||
Node *state_chart_node = nullptr;
|
||||
|
||||
float gravity;
|
||||
|
||||
Vector3 current_movement_vector;
|
||||
|
||||
// Godot boilerplate below
|
||||
protected:
|
||||
static void _bind_methods()
|
||||
{
|
||||
// State chart signal responses
|
||||
BIND_METHOD(MovementHandler, _on_walking_state_entered);
|
||||
BIND_METHOD_1PARAM(MovementHandler, _on_walking_state_physics_processing, delta);
|
||||
|
||||
BIND_METHOD(MovementHandler, _on_airborne_state_entered);
|
||||
BIND_METHOD_1PARAM(MovementHandler, _on_airborne_state_physics_processing, delta);
|
||||
|
||||
BIND_METHOD(MovementHandler, _on_jumping_state_entered);
|
||||
BIND_METHOD(MovementHandler, _on_cancel_jump_state_entered);
|
||||
BIND_METHOD(MovementHandler, _on_double_jump_state_entered);
|
||||
|
||||
BIND_METHOD_2PARAM(MovementHandler, _on_movement_2d, delta, vector);
|
||||
BIND_METHOD(MovementHandler, _on_jump_pressed);
|
||||
BIND_METHOD(MovementHandler, _on_jump_released);
|
||||
|
||||
|
||||
// Movement Properties
|
||||
ADD_GROUP("Ground", "Movement Properties");
|
||||
ADD_GETTER_SETTER_HINTED(MovementHandler, top_speed, Variant::FLOAT, PROPERTY_HINT_RANGE, "0.1,20.0,0.01");
|
||||
|
||||
ADD_SUBGROUP("Acceleration", "Movement Properties");
|
||||
ADD_GETTER_SETTER_HINTED(MovementHandler, ground_acceleration, Variant::FLOAT, PROPERTY_HINT_RANGE, "0.1,20.0,0.01");
|
||||
|
||||
ADD_SUBGROUP("Deceleration", "Movement Properties");
|
||||
ADD_GETTER_SETTER_HINTED(MovementHandler, ground_friction, Variant::FLOAT, PROPERTY_HINT_RANGE, "0.1,20.0,0.01");
|
||||
|
||||
ADD_GROUP("Air", "Movement Properties");
|
||||
ADD_GETTER_SETTER_HINTED(MovementHandler, gravity_multiplier, Variant::FLOAT, PROPERTY_HINT_RANGE, "0.0,10.0,0.1");
|
||||
|
||||
ADD_SUBGROUP("Acceleration", "Movement Properties");
|
||||
ADD_GETTER_SETTER_HINTED(MovementHandler, air_acceleration, Variant::FLOAT, PROPERTY_HINT_RANGE, "0.1,20.0,0.01");
|
||||
|
||||
ADD_SUBGROUP("Deceleration", "Movement Properties");
|
||||
ADD_GETTER_SETTER_HINTED(MovementHandler, air_friction, Variant::FLOAT, PROPERTY_HINT_RANGE, "0.1,20.0,0.01");
|
||||
|
||||
ADD_SUBGROUP("Jumping", "Movement Properties");
|
||||
ADD_GETTER_SETTER_HINTED(MovementHandler, jump_force, Variant::FLOAT, PROPERTY_HINT_RANGE, "0.1,20.0,0.01");
|
||||
ADD_GETTER_SETTER_HINTED(MovementHandler, jump_cancel_force, Variant::FLOAT, PROPERTY_HINT_RANGE, "0.1,20.0,0.01");
|
||||
ADD_GETTER_SETTER_HINTED(MovementHandler, double_jump_force, Variant::FLOAT, PROPERTY_HINT_RANGE, "0.1,20.0,0.01");
|
||||
|
||||
ADD_GROUP("Game Juice", "Movement Properties");
|
||||
ADD_GETTER_SETTER(MovementHandler, jump_stretch_size, Variant::VECTOR3);
|
||||
|
||||
|
||||
// Node references
|
||||
ADD_GETTER_SETTER_HINTED(MovementHandler, controlled_body, Variant::NODE_PATH, PROPERTY_HINT_NODE_PATH_VALID_TYPES, "CharacterBody3D");
|
||||
ADD_GETTER_SETTER_HINTED(MovementHandler, state_chart, Variant::NODE_PATH, PROPERTY_HINT_NODE_PATH_VALID_TYPES, "StateChart");
|
||||
|
||||
|
||||
// Signals
|
||||
ADD_SIGNAL(MethodInfo("state_changed", PropertyInfo(Variant::STRING_NAME, "state")));
|
||||
ADD_SIGNAL(MethodInfo("movement_speed_changed", PropertyInfo(Variant::STRING_NAME, "state"), PropertyInfo(Variant::FLOAT, "speed")));
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* ©2023 Batty Bovine Productions, LLC. All Rights Reserved.
|
||||
*
|
||||
* If this source code makes it to the public internet, this software can be
|
||||
* considered to be protected by the MIT licence. Have fun with it.
|
||||
*/
|
||||
|
||||
#include "player_spawn.h"
|
||||
|
||||
#include "nodes/sublevel_scene.h"
|
||||
#include "singletons/save_manager.h"
|
||||
|
||||
#include <godot_cpp/classes/array_mesh.hpp>
|
||||
#include <godot_cpp/classes/capsule_shape3d.hpp>
|
||||
#include <godot_cpp/classes/character_body3d.hpp>
|
||||
#include <godot_cpp/classes/collision_shape3d.hpp>
|
||||
#include <godot_cpp/classes/engine.hpp>
|
||||
#include <godot_cpp/classes/material.hpp>
|
||||
#include <godot_cpp/classes/mesh.hpp>
|
||||
#include <godot_cpp/classes/mesh_instance3d.hpp>
|
||||
#include <godot_cpp/classes/resource_loader.hpp>
|
||||
#include <godot_cpp/classes/sprite3d.hpp>
|
||||
|
||||
#include <godot_cpp/variant/utility_functions.hpp>
|
||||
using namespace godot;
|
||||
|
||||
|
||||
PlayerSpawn::PlayerSpawn()
|
||||
{
|
||||
this->tag = "spawn_name";
|
||||
}
|
||||
|
||||
void PlayerSpawn::_on_area_3d_body_entered(Node3D *body)
|
||||
{
|
||||
SaveManager *save_manager = SaveManager::get_singleton();
|
||||
if (save_manager->get_current_checkpoint() != this->tag)
|
||||
{
|
||||
save_manager->set_new_checkpoint(this->tag);
|
||||
}
|
||||
this->queue_free();
|
||||
}
|
||||
|
||||
|
||||
bool PlayerOrigin::spawn_player(Node3D *parent, Ref<PackedScene> player) const
|
||||
{
|
||||
Ref<PackedScene> player_scene_to_unpack = this->player_scene.is_valid() ? this->player_scene : player;
|
||||
|
||||
if(player_scene_to_unpack.is_valid())
|
||||
{
|
||||
for (int i = 0; i < this->sublevel_scenes_to_load.size(); i++)
|
||||
{
|
||||
if (SublevelScene *sublevel = cast_to<SublevelScene>(this->get_node_or_null(this->sublevel_scenes_to_load[i])))
|
||||
{
|
||||
sublevel->load_sublevel();
|
||||
}
|
||||
#ifdef DEBUG_ENABLED
|
||||
else
|
||||
{
|
||||
UtilityFunctions::push_error("Empty sublevel in PlayerSpawn object \"", this->tag, "\"");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
Node3D *player_scene = cast_to<Node3D>(player_scene_to_unpack->instantiate());
|
||||
player_scene->set_global_transform(this->get_global_transform());
|
||||
parent->add_child(player_scene);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
* ©2023 Batty Bovine Productions, LLC. All Rights Reserved.
|
||||
*
|
||||
* If this source code makes it to the public internet, this software can be
|
||||
* considered to be protected by the MIT licence. Have fun with it.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "orng_macros.h"
|
||||
|
||||
#include <godot_cpp/classes/node3d.hpp>
|
||||
#include <godot_cpp/classes/packed_scene.hpp>
|
||||
using namespace godot;
|
||||
|
||||
#define PLAYER_ORIGIN_TAG "origin"
|
||||
|
||||
|
||||
// Origin of the level. Player will spawn here by default. This is the parent
|
||||
// to all other spawn points to ensure the tag can be set to the origin tag by
|
||||
// default, and only changed by derived classes.
|
||||
class PlayerOrigin : public Node3D
|
||||
{
|
||||
GDCLASS(PlayerOrigin, Node3D);
|
||||
|
||||
public:
|
||||
// Spawn the player at the spawn point's location. If the spawn point has
|
||||
// no player scene set, the passed in player scene will be used. Function
|
||||
// returns false if no player could be spawned.
|
||||
bool spawn_player(Node3D *parent, Ref<PackedScene> player) const;
|
||||
|
||||
virtual StringName get_tag() const { return this->tag; }
|
||||
|
||||
void set_player_scene(Ref<PackedScene> s) { this->player_scene = s; }
|
||||
Ref<PackedScene> get_player_scene() const { return this->player_scene; }
|
||||
|
||||
void set_sublevel_scenes_to_load(TypedArray<NodePath> scenes) { this->sublevel_scenes_to_load = scenes; }
|
||||
TypedArray<NodePath> get_sublevel_scenes_to_load() const { return this->sublevel_scenes_to_load; }
|
||||
|
||||
protected:
|
||||
StringName tag = StringName(PLAYER_ORIGIN_TAG);
|
||||
|
||||
// Scene to spawn at this location when requested.
|
||||
Ref<PackedScene> player_scene;
|
||||
|
||||
// List of sublevels that need to be loaded along with the player.
|
||||
TypedArray<NodePath> sublevel_scenes_to_load;
|
||||
|
||||
// Godot boilerplate below
|
||||
protected:
|
||||
static void _bind_methods()
|
||||
{
|
||||
BIND_METHOD(PlayerOrigin, get_tag);
|
||||
|
||||
ADD_GETTER_SETTER_HINTED(PlayerOrigin, player_scene, Variant::OBJECT, PROPERTY_HINT_RESOURCE_TYPE, "PackedScene");
|
||||
ADD_GETTER_SETTER_HINTED(PlayerOrigin, sublevel_scenes_to_load, Variant::ARRAY, PROPERTY_HINT_ARRAY_TYPE, vformat("%s/%s:%s", Variant::NODE_PATH, PROPERTY_HINT_NODE_PATH_VALID_TYPES, "SublevelScene"));
|
||||
}
|
||||
};
|
||||
|
||||
class PlayerSpawn : public PlayerOrigin
|
||||
{
|
||||
GDCLASS(PlayerSpawn, PlayerOrigin);
|
||||
|
||||
public:
|
||||
PlayerSpawn();
|
||||
|
||||
virtual void set_tag(StringName t) { this->tag = t; }
|
||||
|
||||
protected:
|
||||
virtual void _on_area_3d_body_entered(class Node3D *body);
|
||||
|
||||
// Godot boilerplate below
|
||||
protected:
|
||||
static void _bind_methods()
|
||||
{
|
||||
BIND_METHOD_1PARAM(PlayerSpawn, set_tag, tag);
|
||||
ADD_PROPERTY(PropertyInfo(Variant::STRING_NAME, "tag"), "set_tag", "get_tag");
|
||||
|
||||
BIND_METHOD_1PARAM(PlayerSpawn, _on_area_3d_body_entered, body);
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* ©2023 Batty Bovine Productions, LLC. All Rights Reserved.
|
||||
*
|
||||
* If this source code makes it to the public internet, this software can be
|
||||
* considered to be protected by the MIT licence. Have fun with it.
|
||||
*/
|
||||
|
||||
#include "sublevel_loader.h"
|
||||
|
||||
|
||||
SublevelLoader::SublevelLoader()
|
||||
{
|
||||
this->set_collision_layer(0b0001);
|
||||
this->set_collision_mask(0b1000);
|
||||
}
|
||||
|
||||
void SublevelLoader::_ready()
|
||||
{
|
||||
this->connect("body_entered", callable_mp(this, &SublevelLoader::_on_sublevel_loader_body_entered), ConnectFlags::CONNECT_ONE_SHOT);
|
||||
}
|
||||
|
||||
void SublevelLoader::_on_sublevel_loader_body_entered(Node3D *body)
|
||||
{
|
||||
// First unload sublevels as necessary...
|
||||
for(int i = 0; i < this->sublevel_scenes_to_unload.size(); i++)
|
||||
{
|
||||
cast_to<SublevelScene>(this->get_node_or_null(this->sublevel_scenes_to_unload[i]))->unload_sublevel();
|
||||
}
|
||||
|
||||
// ...and then once that's done, load the ones we want to load.
|
||||
// We do it in this order because I don't know. It just feels right.
|
||||
for(int i = 0; i < this->sublevel_scenes_to_load.size(); i++)
|
||||
{
|
||||
cast_to<SublevelScene>(this->get_node_or_null(this->sublevel_scenes_to_load[i]))->load_sublevel();
|
||||
}
|
||||
|
||||
this->queue_free();
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* ©2023 Batty Bovine Productions, LLC. All Rights Reserved.
|
||||
*
|
||||
* If this source code makes it to the public internet, this software can be
|
||||
* considered to be protected by the MIT licence. Have fun with it.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "orng_macros.h"
|
||||
|
||||
#include <godot_cpp/classes/area3d.hpp>
|
||||
#include "sublevel_scene.h"
|
||||
using namespace godot;
|
||||
|
||||
|
||||
// Scene that loads a subscene immediately in editor, but only on command
|
||||
// during gameplay.
|
||||
class SublevelLoader : public Area3D
|
||||
{
|
||||
GDCLASS(SublevelLoader, Area3D);
|
||||
|
||||
public:
|
||||
SublevelLoader();
|
||||
virtual void _ready() override;
|
||||
|
||||
void set_sublevel_scenes_to_load(TypedArray<NodePath> scenes) { this->sublevel_scenes_to_load = scenes; }
|
||||
TypedArray<NodePath> get_sublevel_scenes_to_load() const { return this->sublevel_scenes_to_load; }
|
||||
|
||||
void set_sublevel_scenes_to_unload(TypedArray<NodePath> scenes) { this->sublevel_scenes_to_unload = scenes; }
|
||||
TypedArray<NodePath> get_sublevel_scenes_to_unload() const { return this->sublevel_scenes_to_unload; }
|
||||
|
||||
protected:
|
||||
void _on_sublevel_loader_body_entered(Node3D *body);
|
||||
|
||||
TypedArray<NodePath> sublevel_scenes_to_load;
|
||||
TypedArray<NodePath> sublevel_scenes_to_unload;
|
||||
|
||||
// Godot boilerplate below
|
||||
protected:
|
||||
static void _bind_methods()
|
||||
{
|
||||
ADD_GETTER_SETTER_HINTED(SublevelLoader, sublevel_scenes_to_load, Variant::ARRAY, PROPERTY_HINT_ARRAY_TYPE, vformat("%s/%s:%s", Variant::NODE_PATH, PROPERTY_HINT_NODE_PATH_VALID_TYPES, "SublevelScene"));
|
||||
ADD_GETTER_SETTER_HINTED(SublevelLoader, sublevel_scenes_to_unload, Variant::ARRAY, PROPERTY_HINT_ARRAY_TYPE, vformat("%s/%s:%s", Variant::NODE_PATH, PROPERTY_HINT_NODE_PATH_VALID_TYPES, "SublevelScene"));
|
||||
|
||||
BIND_METHOD_1PARAM(SublevelLoader, _on_sublevel_loader_body_entered, body);
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
* ©2023 Batty Bovine Productions, LLC. All Rights Reserved.
|
||||
*
|
||||
* If this source code makes it to the public internet, this software can be
|
||||
* considered to be protected by the MIT licence. Have fun with it.
|
||||
*/
|
||||
|
||||
#include "sublevel_scene.h"
|
||||
#include "singletons/scene_loader.h"
|
||||
|
||||
#include <godot_cpp/classes/engine.hpp>
|
||||
#include <godot_cpp/classes/node3d.hpp>
|
||||
#include <godot_cpp/variant/utility_functions.hpp>
|
||||
using namespace godot;
|
||||
|
||||
|
||||
void SublevelScene::_ready()
|
||||
{
|
||||
#ifdef DEBUG_ENABLED
|
||||
if (Engine::get_singleton()->is_editor_hint())
|
||||
{
|
||||
this->load_sublevel();
|
||||
}
|
||||
#endif // DEBUG_ENABLED
|
||||
}
|
||||
|
||||
void SublevelScene::set_sublevel(StringName s)
|
||||
{
|
||||
this->sublevel = s;
|
||||
|
||||
#ifdef DEBUG_ENABLED
|
||||
if (Engine::get_singleton()->is_editor_hint())
|
||||
{
|
||||
this->unload_sublevel();
|
||||
this->load_sublevel();
|
||||
}
|
||||
#endif // DEBUG_ENABLED
|
||||
}
|
||||
|
||||
|
||||
void SublevelScene::load_sublevel()
|
||||
{
|
||||
if (this->sublevel_node)
|
||||
return;
|
||||
|
||||
if (SceneLoader *scene_loader = SceneLoader::get_singleton())
|
||||
{
|
||||
scene_loader->load_scene(this->sublevel, callable_mp(this, &SublevelScene::_threaded_load_callback));
|
||||
}
|
||||
}
|
||||
void SublevelScene::_threaded_load_callback(Ref<PackedScene> loaded_scene)
|
||||
{
|
||||
if (loaded_scene.is_valid())
|
||||
{
|
||||
this->sublevel_node = cast_to<Node3D>(loaded_scene->instantiate());
|
||||
this->add_child(this->sublevel_node);
|
||||
}
|
||||
}
|
||||
|
||||
void SublevelScene::unload_sublevel()
|
||||
{
|
||||
if (this->sublevel_node)
|
||||
{
|
||||
this->sublevel_node->queue_free();
|
||||
this->sublevel_node = nullptr;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* ©2023 Batty Bovine Productions, LLC. All Rights Reserved.
|
||||
*
|
||||
* If this source code makes it to the public internet, this software can be
|
||||
* considered to be protected by the MIT licence. Have fun with it.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "orng_macros.h"
|
||||
|
||||
#include <godot_cpp/classes/node3d.hpp>
|
||||
#include <godot_cpp/classes/packed_scene.hpp>
|
||||
using namespace godot;
|
||||
|
||||
|
||||
// Scene that loads a subscene immediately in editor, but only on command
|
||||
// during gameplay.
|
||||
class SublevelScene : public Node3D
|
||||
{
|
||||
GDCLASS(SublevelScene, Node3D);
|
||||
|
||||
public:
|
||||
virtual void _ready() override;
|
||||
|
||||
void set_sublevel(StringName s);
|
||||
StringName get_sublevel() const { return this->sublevel; }
|
||||
|
||||
void load_sublevel();
|
||||
void unload_sublevel();
|
||||
|
||||
protected:
|
||||
StringName sublevel;
|
||||
|
||||
private:
|
||||
void _threaded_load_callback(Ref<PackedScene> loaded_scene);
|
||||
|
||||
Node3D *sublevel_node = nullptr;
|
||||
|
||||
// Godot boilerplate below
|
||||
protected:
|
||||
static void _bind_methods()
|
||||
{
|
||||
ADD_GETTER_SETTER_HINTED(SublevelScene, sublevel, Variant::STRING, PROPERTY_HINT_FILE, "*.scn,*.tscn");
|
||||
|
||||
BIND_METHOD(SublevelScene, load_sublevel);
|
||||
BIND_METHOD(SublevelScene, unload_sublevel);
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,30 @@
|
||||
#pragma once
|
||||
|
||||
/**
|
||||
* Method and property binding helpers
|
||||
*/
|
||||
#define BIND_METHOD(Class, MethodName) \
|
||||
ClassDB::bind_method(D_METHOD(#MethodName), &Class::##MethodName)
|
||||
#define BIND_METHOD_1PARAM(Class, MethodName, P1) \
|
||||
ClassDB::bind_method(D_METHOD(#MethodName, #P1), &Class::##MethodName)
|
||||
#define BIND_METHOD_2PARAM(Class, MethodName, P1, P2) \
|
||||
ClassDB::bind_method(D_METHOD(#MethodName, #P1, #P2), &Class::##MethodName)
|
||||
#define BIND_METHOD_3PARAM(Class, MethodName, P1, P2, P3) \
|
||||
ClassDB::bind_method(D_METHOD(#MethodName, #P1, #P2, #P3), &Class::##MethodName)
|
||||
|
||||
#define ADD_GETTER_SETTER(Class, Property, VariantType) \
|
||||
ClassDB::bind_method(D_METHOD("get_"#Property), &Class::get_##Property); \
|
||||
ClassDB::bind_method(D_METHOD("set_"#Property, #Property), &Class::set_##Property); \
|
||||
ADD_PROPERTY(PropertyInfo(VariantType, #Property), "set_"#Property, "get_"#Property)
|
||||
|
||||
#define ADD_GETTER_SETTER_HINTED(Class, Property, VariantType, Hint, HintString) \
|
||||
ClassDB::bind_method(D_METHOD("get_"#Property), &Class::get_##Property); \
|
||||
ClassDB::bind_method(D_METHOD("set_"#Property, #Property), &Class::set_##Property); \
|
||||
ADD_PROPERTY(PropertyInfo(VariantType, #Property, Hint, HintString), "set_"#Property, "get_"#Property)
|
||||
|
||||
|
||||
/**
|
||||
* Deferred function helpers
|
||||
*/
|
||||
#define CALL_NEXT_FRAME(C) \
|
||||
this->get_tree()->create_timer(0.001)->connect("timeout", C)
|
||||
@@ -0,0 +1,102 @@
|
||||
/* godot-cpp integration testing project.
|
||||
*
|
||||
* This is free and unencumbered software released into the public domain.
|
||||
*/
|
||||
|
||||
#include "register_types.h"
|
||||
|
||||
#include <gdextension_interface.h>
|
||||
|
||||
#include <godot_cpp/classes/engine.hpp>
|
||||
#include <godot_cpp/core/class_db.hpp>
|
||||
#include <godot_cpp/core/defs.hpp>
|
||||
#include <godot_cpp/variant/utility_functions.hpp>
|
||||
#include <godot_cpp/godot.hpp>
|
||||
|
||||
#include "nodes/controller_menu.h"
|
||||
#include "nodes/hide_in_game.h"
|
||||
#include "nodes/level_scene.h"
|
||||
#include "nodes/menu_stack.h"
|
||||
#include "nodes/movement_handler.h"
|
||||
#include "nodes/player_spawn.h"
|
||||
#include "nodes/sublevel_loader.h"
|
||||
#include "nodes/sublevel_scene.h"
|
||||
|
||||
#include "resources/level_metadata_map.h"
|
||||
#include "resources/level_metadata_resource.h"
|
||||
|
||||
#include "singletons/input_handler.h"
|
||||
#include "singletons/save_manager.h"
|
||||
#include "singletons/scene_loader.h"
|
||||
#include "singletons/vendor_service/vendor_service.h"
|
||||
|
||||
using namespace godot;
|
||||
|
||||
GDSINGLETON_REGISTER_PTR(InputHandler, _input_handler_singleton);
|
||||
GDSINGLETON_REGISTER_PTR(SaveManager, _save_manager_singleton);
|
||||
GDSINGLETON_REGISTER_PTR(SceneLoader, _scene_loader_singleton);
|
||||
GDSINGLETON_REGISTER_PTR(VendorService, _vendor_service_singleton);
|
||||
|
||||
|
||||
void initialize_orng_module(ModuleInitializationLevel p_level) {
|
||||
if (p_level == ModuleInitializationLevel::MODULE_INITIALIZATION_LEVEL_SCENE)
|
||||
{
|
||||
ClassDB::register_class<HideInGame>();
|
||||
|
||||
ClassDB::register_class<ControllerMenu>();
|
||||
ClassDB::register_class<MenuStack>();
|
||||
|
||||
ClassDB::register_class<SublevelLoader>();
|
||||
ClassDB::register_class<SublevelScene>();
|
||||
|
||||
ClassDB::register_class<InputAction>();
|
||||
ClassDB::register_class<InputAxis>();
|
||||
ClassDB::register_class<InputAxis2D>();
|
||||
ClassDB::register_class<InputResource>();
|
||||
|
||||
ClassDB::register_class<LevelMetadataMap>();
|
||||
ClassDB::register_class<LevelMetadataMapEntry>();
|
||||
ClassDB::register_class<LevelMetadataResource>();
|
||||
|
||||
ClassDB::register_class<MovementHandler>();
|
||||
|
||||
ClassDB::register_class<GameClasses>();
|
||||
ClassDB::register_class<LevelScene>();
|
||||
ClassDB::register_class<PlayerOrigin>();
|
||||
ClassDB::register_class<PlayerSpawn>();
|
||||
|
||||
ClassDB::register_class<SaveFileData>();
|
||||
|
||||
GDSINGLETON_REGISTER_CLASS(InputHandler, _input_handler_singleton);
|
||||
GDSINGLETON_REGISTER_CLASS(SaveManager, _save_manager_singleton);
|
||||
GDSINGLETON_REGISTER_CLASS(SceneLoader, _scene_loader_singleton);
|
||||
GDSINGLETON_REGISTER_CLASS(VendorService, _vendor_service_singleton);
|
||||
}
|
||||
}
|
||||
|
||||
void uninitialize_orng_module(ModuleInitializationLevel p_level) {
|
||||
if (p_level == ModuleInitializationLevel::MODULE_INITIALIZATION_LEVEL_SCENE)
|
||||
{
|
||||
if (Engine::get_singleton()->get_main_loop())
|
||||
{
|
||||
GDSINGLETON_UNREGISTER_CLASS(_vendor_service_singleton);
|
||||
GDSINGLETON_UNREGISTER_CLASS(_scene_loader_singleton);
|
||||
GDSINGLETON_UNREGISTER_CLASS(_save_manager_singleton);
|
||||
GDSINGLETON_UNREGISTER_CLASS(_input_handler_singleton);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
// Initialization.
|
||||
GDExtensionBool GDE_EXPORT orng_library_init(GDExtensionInterfaceGetProcAddress p_get_proc_address, GDExtensionClassLibraryPtr p_library, GDExtensionInitialization *r_initialization)
|
||||
{
|
||||
godot::GDExtensionBinding::InitObject init_obj(p_get_proc_address, p_library, r_initialization);
|
||||
|
||||
init_obj.register_initializer(initialize_orng_module);
|
||||
init_obj.register_terminator(uninitialize_orng_module);
|
||||
init_obj.set_minimum_library_initialization_level(ModuleInitializationLevel::MODULE_INITIALIZATION_LEVEL_SCENE);
|
||||
|
||||
return init_obj.init();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
/* godot-cpp integration testing project.
|
||||
*
|
||||
* This is free and unencumbered software released into the public domain.
|
||||
*/
|
||||
|
||||
#ifndef _REGISTER_TYPES_H__
|
||||
#define _REGISTER_TYPES_H__
|
||||
|
||||
#include <godot_cpp/core/class_db.hpp>
|
||||
using namespace godot;
|
||||
|
||||
void initialize_orng_module(ModuleInitializationLevel p_level);
|
||||
void uninitialize_orng_module(ModuleInitializationLevel p_level);
|
||||
|
||||
#endif // _REGISTER_TYPES_H__
|
||||
@@ -0,0 +1,4 @@
|
||||
[ViewState]
|
||||
Mode=
|
||||
Vid=
|
||||
FolderType=Generic
|
||||
@@ -0,0 +1,101 @@
|
||||
/*
|
||||
* ©2024 Batty Bovine Productions, LLC. All Rights Reserved.
|
||||
*
|
||||
* If this source code makes it to the public internet, this software can be
|
||||
* considered to be protected by the MIT licence. Have fun with it.
|
||||
*/
|
||||
|
||||
#include "resources/input_resource.h"
|
||||
|
||||
#include <godot_cpp/classes/input.hpp>
|
||||
using namespace godot;
|
||||
|
||||
|
||||
void InputResource::update_buttons(const Ref<InputEvent> event, const float delta)
|
||||
{
|
||||
for (uint8_t i = 0; i < this->input_actions.size(); i++)
|
||||
{
|
||||
const InputAction *action = cast_to<InputAction>(this->input_actions[i]);
|
||||
|
||||
const StringName suffix_pressed = StringName("_on_") + action->get_action_name() + INPUTACTION_SUFFIX_PRESSED;
|
||||
if (event->is_action_pressed(action->get_action_name()) && this->has_signal(suffix_pressed))
|
||||
this->emit_signal(suffix_pressed);
|
||||
|
||||
const StringName suffix_released = StringName("_on_") + action->get_action_name() + INPUTACTION_SUFFIX_RELEASED;
|
||||
if (event->is_action_released(action->get_action_name()) && this->has_signal(suffix_released))
|
||||
this->emit_signal(suffix_released);
|
||||
}
|
||||
}
|
||||
|
||||
void InputResource::update_axes(const float delta)
|
||||
{
|
||||
for (uint8_t i = 0; i < this->input_axes.size(); i++)
|
||||
{
|
||||
const InputAxis *axis = cast_to<InputAxis>(this->input_axes[i]);
|
||||
const StringName prefix = StringName("_on_") + axis->get_axis_name();
|
||||
|
||||
const StringName signal_1d = prefix + INPUTAXIS_ONE_DIMENSIONAL_SUFFIX;
|
||||
if (this->has_signal(signal_1d)) this->emit_signal(signal_1d, delta, axis->get_x_axis());
|
||||
|
||||
const StringName signal_2d = prefix + INPUTAXIS_TWO_DIMENSIONAL_SUFFIX;
|
||||
if (this->has_signal(signal_2d)) this->emit_signal(signal_2d, delta, axis->get_lateral_vector());
|
||||
|
||||
const StringName signal_3d = prefix + INPUTAXIS_THREE_DIMENSIONAL_SUFFIX;
|
||||
if (this->has_signal(signal_3d)) this->emit_signal(signal_3d, delta, axis->get_spherical_vector());
|
||||
}
|
||||
}
|
||||
|
||||
void InputResource::update_axes_mouse_event(const Ref<InputEventMouseMotion> event, const float delta)
|
||||
{
|
||||
for (uint8_t i = 0; i < this->input_axes.size(); i++)
|
||||
{
|
||||
const InputAxis *axis = cast_to<InputAxis>(this->input_axes[i]);
|
||||
const StringName signal = StringName("_on_") + axis->get_axis_name() + INPUTAXIS_TWO_DIMENSIONAL_SUFFIX;
|
||||
if (axis->get_include_mouse() && this->has_signal(signal))
|
||||
this->emit_signal(signal, delta, Vector2(event->get_relative().x, event->get_relative().y) * 0.001 / delta);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Vector3 InputAxis::get_spherical_vector() const
|
||||
{
|
||||
const Input *input = Input::get_singleton();
|
||||
return Vector3(
|
||||
input->get_axis(this->axis_name + INPUTAXIS_LEFT_SUFFIX, this->axis_name + INPUTAXIS_RIGHT_SUFFIX),
|
||||
input->get_axis(this->axis_name + INPUTAXIS_DOWN_SUFFIX, this->axis_name + INPUTAXIS_UP_SUFFIX),
|
||||
input->get_axis(this->axis_name + INPUTAXIS_FORWARD_SUFFIX, this->axis_name + INPUTAXIS_BACK_SUFFIX)
|
||||
);
|
||||
}
|
||||
|
||||
Vector2 InputAxis::get_lateral_vector() const
|
||||
{
|
||||
return Input::get_singleton()->get_vector(
|
||||
this->axis_name + INPUTAXIS_LEFT_SUFFIX, this->axis_name + INPUTAXIS_RIGHT_SUFFIX,
|
||||
this->axis_name + INPUTAXIS_FORWARD_SUFFIX, this->axis_name + INPUTAXIS_BACK_SUFFIX
|
||||
);
|
||||
}
|
||||
|
||||
Vector2 InputAxis::get_lateral_vector_square() const
|
||||
{
|
||||
const Input *input = Input::get_singleton();
|
||||
return Vector2(
|
||||
input->get_axis(this->axis_name + INPUTAXIS_LEFT_SUFFIX, this->axis_name + INPUTAXIS_RIGHT_SUFFIX),
|
||||
input->get_axis(this->axis_name + INPUTAXIS_FORWARD_SUFFIX, this->axis_name + INPUTAXIS_BACK_SUFFIX)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
float InputAxis::get_x_axis() const
|
||||
{
|
||||
return Input::get_singleton()->get_axis(this->axis_name + INPUTAXIS_LEFT_SUFFIX, this->axis_name + INPUTAXIS_RIGHT_SUFFIX);
|
||||
}
|
||||
|
||||
float InputAxis::get_y_axis() const
|
||||
{
|
||||
return Input::get_singleton()->get_axis(this->axis_name + INPUTAXIS_DOWN_SUFFIX, this->axis_name + INPUTAXIS_UP_SUFFIX);
|
||||
}
|
||||
|
||||
float InputAxis::get_z_axis() const
|
||||
{
|
||||
return Input::get_singleton()->get_axis(this->axis_name + INPUTAXIS_FORWARD_SUFFIX, this->axis_name + INPUTAXIS_BACK_SUFFIX);
|
||||
}
|
||||
@@ -0,0 +1,208 @@
|
||||
/*
|
||||
* ©2024 Batty Bovine Productions, LLC. All Rights Reserved.
|
||||
*
|
||||
* If this source code makes it to the public internet, this software can be
|
||||
* considered to be protected by the MIT licence. Have fun with it.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "orng_macros.h"
|
||||
|
||||
#include <godot_cpp/classes/input_event_mouse_motion.hpp>
|
||||
#include <godot_cpp/classes/resource.hpp>
|
||||
using namespace godot;
|
||||
|
||||
#define INPUTACTION_SUFFIX_PRESSED StringName("_pressed")
|
||||
#define INPUTACTION_SUFFIX_RELEASED StringName("_released")
|
||||
|
||||
#define INPUTAXIS_UP_SUFFIX StringName("_up")
|
||||
#define INPUTAXIS_DOWN_SUFFIX StringName("_down")
|
||||
#define INPUTAXIS_LEFT_SUFFIX StringName("_left")
|
||||
#define INPUTAXIS_RIGHT_SUFFIX StringName("_right")
|
||||
#define INPUTAXIS_FORWARD_SUFFIX StringName("_forward")
|
||||
#define INPUTAXIS_BACK_SUFFIX StringName("_back")
|
||||
|
||||
#define INPUTAXIS_ONE_DIMENSIONAL_SUFFIX StringName("_1d")
|
||||
#define INPUTAXIS_TWO_DIMENSIONAL_SUFFIX StringName("_2d")
|
||||
#define INPUTAXIS_THREE_DIMENSIONAL_SUFFIX StringName("_3d")
|
||||
|
||||
|
||||
class InputAxis2D : public Resource
|
||||
{
|
||||
GDCLASS(InputAxis2D, Resource);
|
||||
|
||||
public:
|
||||
void set_negative(Ref<InputEvent> n) { this->negative = n; }
|
||||
Ref<InputEvent> get_negative() const { return this->negative; }
|
||||
|
||||
void set_positive(Ref<InputEvent> p) { this->positive = p; }
|
||||
Ref<InputEvent> get_positive() const { return this->positive; }
|
||||
|
||||
protected:
|
||||
Ref<InputEvent> negative;
|
||||
Ref<InputEvent> positive;
|
||||
|
||||
// Godot boilerplate below
|
||||
protected:
|
||||
static void _bind_methods()
|
||||
{
|
||||
ADD_GETTER_SETTER_HINTED(InputAxis2D, negative, Variant::OBJECT, PROPERTY_HINT_RESOURCE_TYPE, "InputEvent");
|
||||
ADD_GETTER_SETTER_HINTED(InputAxis2D, positive, Variant::OBJECT, PROPERTY_HINT_RESOURCE_TYPE, "InputEvent");
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
class InputAction : public Resource
|
||||
{
|
||||
GDCLASS(InputAction, Resource);
|
||||
|
||||
public:
|
||||
void set_action_name(const StringName name) { this->action_name = name; }
|
||||
StringName get_action_name() const { return this->action_name; }
|
||||
|
||||
void set_deadzone(const float zone) { this->deadzone = zone; }
|
||||
float get_deadzone() const { return this->deadzone; }
|
||||
|
||||
void set_input_events(const TypedArray<InputEvent> events) { this->input_events = events; }
|
||||
TypedArray<InputEvent> get_input_events() const { return this->input_events; }
|
||||
|
||||
protected:
|
||||
StringName action_name = "action_name";
|
||||
float deadzone = 0.5f;
|
||||
TypedArray<InputEvent> input_events;
|
||||
|
||||
// Godot boilerplate below
|
||||
protected:
|
||||
static void _bind_methods()
|
||||
{
|
||||
ADD_GETTER_SETTER(InputAction, action_name, Variant::STRING_NAME);
|
||||
ADD_GETTER_SETTER(InputAction, deadzone, Variant::FLOAT);
|
||||
ADD_GETTER_SETTER_HINTED(InputAction, input_events, Variant::ARRAY, PROPERTY_HINT_ARRAY_TYPE, vformat("%s/%s:%s", Variant::OBJECT, PROPERTY_HINT_RESOURCE_TYPE, "InputEvent"));
|
||||
}
|
||||
};
|
||||
|
||||
class InputAxis : public Resource
|
||||
{
|
||||
GDCLASS(InputAxis, Resource);
|
||||
|
||||
public:
|
||||
enum AXIS_DIMENSIONS
|
||||
{
|
||||
ONE,
|
||||
TWO,
|
||||
THREE
|
||||
};
|
||||
|
||||
void set_axis_name(const StringName name) { this->axis_name = name; }
|
||||
StringName get_axis_name() const { return this->axis_name; }
|
||||
|
||||
void set_axis_dimensions(const uint8_t dimensions) { this->axis_dimensions = (AXIS_DIMENSIONS)dimensions; }
|
||||
uint8_t get_axis_dimensions() const { return (uint8_t)this->axis_dimensions; }
|
||||
|
||||
void set_deadzone(const float deadzone) { this->deadzone = deadzone; }
|
||||
float get_deadzone() const { return this->deadzone; }
|
||||
|
||||
void set_include_mouse(const bool include) { this->include_mouse = include; }
|
||||
bool get_include_mouse() const { return this->include_mouse; }
|
||||
|
||||
|
||||
void set_left_right_events(const TypedArray<InputAxis2D> events) { this->left_right_events = events; }
|
||||
TypedArray<InputAxis2D> get_left_right_events() const { return this->left_right_events; }
|
||||
|
||||
void set_forward_back_events(const TypedArray<InputAxis2D> events) { this->forward_back_events = events; }
|
||||
TypedArray<InputAxis2D> get_forward_back_events() const { return this->forward_back_events; }
|
||||
|
||||
void set_up_down_events(const TypedArray<InputAxis2D> events) { this->up_down_events = events; }
|
||||
TypedArray<InputAxis2D> get_up_down_events() const { return this->up_down_events; }
|
||||
|
||||
|
||||
Vector3 get_spherical_vector() const;
|
||||
Vector2 get_lateral_vector() const;
|
||||
Vector2 get_lateral_vector_square() const;
|
||||
|
||||
float get_x_axis() const;
|
||||
float get_y_axis() const;
|
||||
float get_z_axis() const;
|
||||
|
||||
protected:
|
||||
StringName axis_name = "axis_name";
|
||||
AXIS_DIMENSIONS axis_dimensions = AXIS_DIMENSIONS::TWO;
|
||||
float deadzone = 0.15f;
|
||||
bool include_mouse = false;
|
||||
|
||||
TypedArray<InputAxis2D> left_right_events;
|
||||
TypedArray<InputAxis2D> forward_back_events;
|
||||
TypedArray<InputAxis2D> up_down_events;
|
||||
|
||||
// Godot boilerplate below
|
||||
protected:
|
||||
static void _bind_methods()
|
||||
{
|
||||
{ // action_name, axis_dimensions, deadzone, include_mouse
|
||||
ADD_GETTER_SETTER(InputAxis, axis_name, Variant::STRING_NAME);
|
||||
ADD_GETTER_SETTER_HINTED(InputAxis, axis_dimensions, Variant::INT, PROPERTY_HINT_ENUM, "One,Two,Three");
|
||||
ADD_GETTER_SETTER(InputAxis, deadzone, Variant::FLOAT);
|
||||
ADD_GETTER_SETTER(InputAxis, include_mouse, Variant::BOOL);
|
||||
}
|
||||
|
||||
{ // left_right_events, forward_back_events, up_down_events
|
||||
ADD_GETTER_SETTER_HINTED(InputAxis, left_right_events, Variant::ARRAY, PROPERTY_HINT_ARRAY_TYPE, vformat("%s/%s:%s", Variant::OBJECT, PROPERTY_HINT_RESOURCE_TYPE, "InputAxis2D"));
|
||||
ADD_GETTER_SETTER_HINTED(InputAxis, forward_back_events, Variant::ARRAY, PROPERTY_HINT_ARRAY_TYPE, vformat("%s/%s:%s", Variant::OBJECT, PROPERTY_HINT_RESOURCE_TYPE, "InputAxis2D"));
|
||||
ADD_GETTER_SETTER_HINTED(InputAxis, up_down_events, Variant::ARRAY, PROPERTY_HINT_ARRAY_TYPE, vformat("%s/%s:%s", Variant::OBJECT, PROPERTY_HINT_RESOURCE_TYPE, "InputAxis2D"));
|
||||
}
|
||||
|
||||
{ // Axis getters
|
||||
BIND_METHOD(InputAxis, get_spherical_vector);
|
||||
BIND_METHOD(InputAxis, get_lateral_vector);
|
||||
BIND_METHOD(InputAxis, get_lateral_vector_square);
|
||||
|
||||
BIND_METHOD(InputAxis, get_x_axis);
|
||||
BIND_METHOD(InputAxis, get_y_axis);
|
||||
BIND_METHOD(InputAxis, get_z_axis);
|
||||
}
|
||||
|
||||
{ // AXIS_DIMENSIONS enum constants
|
||||
BIND_ENUM_CONSTANT(AXIS_DIMENSIONS::ONE);
|
||||
BIND_ENUM_CONSTANT(AXIS_DIMENSIONS::TWO);
|
||||
BIND_ENUM_CONSTANT(AXIS_DIMENSIONS::THREE);
|
||||
}
|
||||
}
|
||||
};
|
||||
VARIANT_ENUM_CAST(InputAxis::AXIS_DIMENSIONS);
|
||||
|
||||
class InputResource : public Resource
|
||||
{
|
||||
GDCLASS(InputResource, Resource);
|
||||
|
||||
public:
|
||||
void update_buttons(const Ref<InputEvent> event, const float delta);
|
||||
void update_axes(const float delta);
|
||||
void update_axes_mouse_event(const Ref<InputEventMouseMotion> event, const float delta);
|
||||
|
||||
void set_input_actions(const TypedArray<InputAction> input_actions) { this->input_actions = input_actions; }
|
||||
TypedArray<InputAction> get_input_actions() const { return this->input_actions; }
|
||||
|
||||
void set_input_axes(const TypedArray<InputAxis> input_axes) { this->input_axes = input_axes; }
|
||||
TypedArray<InputAxis> get_input_axes() const { return this->input_axes; }
|
||||
|
||||
protected:
|
||||
TypedArray<InputAction> input_actions;
|
||||
TypedArray<InputAxis> input_axes;
|
||||
|
||||
// Godot boilerplate below
|
||||
protected:
|
||||
static void _bind_methods()
|
||||
{
|
||||
{ // input_actions, input_axes
|
||||
ADD_GETTER_SETTER_HINTED(InputResource, input_actions, Variant::ARRAY, PROPERTY_HINT_ARRAY_TYPE, vformat("%s/%s:%s", Variant::OBJECT, PROPERTY_HINT_RESOURCE_TYPE, "InputAction"));
|
||||
ADD_GETTER_SETTER_HINTED(InputResource, input_axes, Variant::ARRAY, PROPERTY_HINT_ARRAY_TYPE, vformat("%s/%s:%s", Variant::OBJECT, PROPERTY_HINT_RESOURCE_TYPE, "InputAxis"));
|
||||
}
|
||||
|
||||
{ // update
|
||||
BIND_METHOD_2PARAM(InputResource, update_buttons, event, delta);
|
||||
BIND_METHOD_1PARAM(InputResource, update_axes, delta);
|
||||
BIND_METHOD_2PARAM(InputResource, update_axes_mouse_event, event, delta);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* ©2024 Batty Bovine Productions, LLC. All Rights Reserved.
|
||||
*
|
||||
* If this source code makes it to the public internet, this software can be
|
||||
* considered to be protected by the MIT licence. Have fun with it.
|
||||
*/
|
||||
|
||||
#include "resources/level_metadata_map.h"
|
||||
|
||||
|
||||
Ref<LevelMetadataMapEntry> LevelMetadataMap::operator[](Ref<PackedScene> key)
|
||||
{
|
||||
for (uint32_t i = 0; i < this->entries.size(); i++)
|
||||
{
|
||||
const Ref<LevelMetadataMapEntry> entry = this->entries[i];
|
||||
if(entry->get_scene() == key)
|
||||
{
|
||||
return entry;
|
||||
}
|
||||
}
|
||||
return Ref<LevelMetadataMapEntry>();
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* ©2024 Batty Bovine Productions, LLC. All Rights Reserved.
|
||||
*
|
||||
* If this source code makes it to the public internet, this software can be
|
||||
* considered to be protected by the MIT licence. Have fun with it.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "level_metadata_resource.h"
|
||||
|
||||
#include <godot_cpp/classes/packed_scene.hpp>
|
||||
using namespace godot;
|
||||
|
||||
|
||||
//
|
||||
class LevelMetadataMapEntry : public Resource
|
||||
{
|
||||
GDCLASS(LevelMetadataMapEntry, Resource);
|
||||
|
||||
public:
|
||||
void set_scene(Ref<PackedScene> scene) { this->scene = scene; }
|
||||
Ref<PackedScene> get_scene() const { return this->scene; }
|
||||
|
||||
void set_metadata(Ref<LevelMetadataResource> metadata) { this->metadata = metadata; }
|
||||
Ref<LevelMetadataResource> get_metadata() const { return this->metadata; }
|
||||
|
||||
protected:
|
||||
Ref<PackedScene> scene;
|
||||
Ref<LevelMetadataResource> metadata;
|
||||
|
||||
// Godot boilerplate below
|
||||
protected:
|
||||
static void _bind_methods()
|
||||
{
|
||||
ADD_GETTER_SETTER_HINTED(LevelMetadataMapEntry, scene, Variant::OBJECT, PROPERTY_HINT_RESOURCE_TYPE, "PackedScene");
|
||||
ADD_GETTER_SETTER_HINTED(LevelMetadataMapEntry, metadata, Variant::OBJECT, PROPERTY_HINT_RESOURCE_TYPE, "LevelMetadataResource");
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
//
|
||||
class LevelMetadataMap : public Resource
|
||||
{
|
||||
GDCLASS(LevelMetadataMap, Resource);
|
||||
|
||||
public:
|
||||
void set_entries(const TypedArray<LevelMetadataMapEntry> entries) { this->entries = entries; }
|
||||
TypedArray<LevelMetadataMapEntry> get_entries() const { return this->entries; }
|
||||
|
||||
Ref<LevelMetadataMapEntry> operator[](Ref<PackedScene> key);
|
||||
|
||||
protected:
|
||||
TypedArray<LevelMetadataMapEntry> entries;
|
||||
|
||||
// Godot boilerplate below
|
||||
protected:
|
||||
static void _bind_methods()
|
||||
{
|
||||
ADD_GETTER_SETTER_HINTED(LevelMetadataMap, entries, Variant::ARRAY, PROPERTY_HINT_ARRAY_TYPE, vformat("%s/%s:%s", Variant::OBJECT, PROPERTY_HINT_RESOURCE_TYPE, "LevelMetadataMapEntry"));
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,11 @@
|
||||
/*
|
||||
* ©2024 Batty Bovine Productions, LLC. All Rights Reserved.
|
||||
*
|
||||
* If this source code makes it to the public internet, this software can be
|
||||
* considered to be protected by the MIT licence. Have fun with it.
|
||||
*/
|
||||
|
||||
#include "resources/level_metadata_resource.h"
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* ©2024 Batty Bovine Productions, LLC. All Rights Reserved.
|
||||
*
|
||||
* If this source code makes it to the public internet, this software can be
|
||||
* considered to be protected by the MIT licence. Have fun with it.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "orng_macros.h"
|
||||
|
||||
#include <godot_cpp/classes/image.hpp>
|
||||
#include <godot_cpp/classes/resource.hpp>
|
||||
using namespace godot;
|
||||
|
||||
|
||||
// A list of scenes to be used as the default in scenarios where it might not
|
||||
// be obvious.
|
||||
class LevelMetadataResource : public Resource
|
||||
{
|
||||
GDCLASS(LevelMetadataResource, Resource);
|
||||
|
||||
public:
|
||||
void set_thumbnail(Ref<Image> thumbnail) { this->thumbnail = thumbnail; }
|
||||
Ref<Image> get_thumbnail() const { return this->thumbnail; }
|
||||
|
||||
void set_level_name(const StringName name) { this->level_name = name; }
|
||||
StringName get_level_name() const { return this->level_name; }
|
||||
|
||||
protected:
|
||||
Ref<Image> thumbnail;
|
||||
StringName level_name;
|
||||
|
||||
// Godot boilerplate below
|
||||
protected:
|
||||
static void _bind_methods()
|
||||
{
|
||||
ADD_GETTER_SETTER_HINTED(LevelMetadataResource, thumbnail, Variant::OBJECT, PROPERTY_HINT_RESOURCE_TYPE, "Image");
|
||||
ADD_GETTER_SETTER(LevelMetadataResource, level_name, Variant::STRING_NAME);
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,4 @@
|
||||
[ViewState]
|
||||
Mode=
|
||||
Vid=
|
||||
FolderType=Generic
|
||||
@@ -0,0 +1,264 @@
|
||||
/*
|
||||
* ©2023 Batty Bovine Productions, LLC. All Rights Reserved.
|
||||
*
|
||||
* If this source code makes it to the public internet, this software can be
|
||||
* considered to be protected by the MIT licence. Have fun with it.
|
||||
*/
|
||||
|
||||
#include "input_handler.h"
|
||||
|
||||
#include <godot_cpp/classes/engine.hpp>
|
||||
#include <godot_cpp/classes/input.hpp>
|
||||
#include <godot_cpp/classes/input_event_mouse_motion.hpp>
|
||||
#include <godot_cpp/classes/input_map.hpp>
|
||||
#include <godot_cpp/classes/scene_tree.hpp>
|
||||
#include <godot_cpp/classes/window.hpp>
|
||||
#include <godot_cpp/variant/utility_functions.hpp>
|
||||
using namespace godot;
|
||||
|
||||
GDSINGLETON_CPP(InputHandler);
|
||||
|
||||
|
||||
void InputHandler::_process(double delta)
|
||||
{
|
||||
for (InputResource *resource : this->active_resources)
|
||||
{
|
||||
resource->update_axes(delta);
|
||||
}
|
||||
}
|
||||
|
||||
void InputHandler::_input(const Ref<InputEvent> &event)
|
||||
{
|
||||
for (InputResource *resource : this->active_resources)
|
||||
{
|
||||
if (event->is_action_type())
|
||||
{
|
||||
resource->update_buttons(event, this->get_process_delta_time());
|
||||
}
|
||||
else if (const InputEventMouseMotion *mouse_event = cast_to<InputEventMouseMotion>(event.ptr()))
|
||||
{
|
||||
const Input *input = Input::get_singleton();
|
||||
if (input->get_mouse_mode() != Input::MOUSE_MODE_VISIBLE)
|
||||
{
|
||||
resource->update_axes_mouse_event(event, this->get_process_delta_time());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void InputHandler::add_input_resource(Node *target, InputResource *resource)
|
||||
{
|
||||
InputMap *input_map = InputMap::get_singleton();
|
||||
const TypedArray<InputAction> &input_actions = resource->get_input_actions();
|
||||
const TypedArray<InputAxis> &input_axes = resource->get_input_axes();
|
||||
|
||||
for(uint8_t i = 0; i < input_actions.size(); i++)
|
||||
{
|
||||
if (const InputAction *action = cast_to<InputAction>(input_actions[i]))
|
||||
{
|
||||
if (!input_map->has_action(action->get_action_name()))
|
||||
{
|
||||
input_map->add_action(action->get_action_name(), action->get_deadzone());
|
||||
}
|
||||
|
||||
for(uint8_t s = 0; s < 2; s++)
|
||||
{
|
||||
const StringName signal_name = StringName("_on_") + action->get_action_name() + this->input_action_suffixes[s];
|
||||
if (target->has_method(signal_name))
|
||||
{
|
||||
const TypedArray<InputEvent> &events = action->get_input_events();
|
||||
if (!resource->has_signal(signal_name))
|
||||
{
|
||||
resource->add_user_signal(signal_name);
|
||||
}
|
||||
for (uint8_t e = 0; e < events.size(); e++)
|
||||
{
|
||||
const Ref<InputEvent> &event = events[e];
|
||||
input_map->action_add_event(action->get_action_name(), event);
|
||||
}
|
||||
resource->connect(signal_name, Callable(target, signal_name));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for(uint8_t i = 0; i < input_axes.size(); i++)
|
||||
{
|
||||
if (const InputAxis *axis = cast_to<InputAxis>(input_axes[i]))
|
||||
{
|
||||
const uint8_t &axis_dimensions = axis->get_axis_dimensions();
|
||||
|
||||
for(uint8_t s = 0; s <= axis_dimensions; s++)
|
||||
{
|
||||
const StringName signal_name = StringName("_on_") + axis->get_axis_name() + this->input_axis_suffixes[s];
|
||||
if (!resource->has_signal(signal_name))
|
||||
{
|
||||
resource->add_user_signal(signal_name);
|
||||
}
|
||||
if (target->has_method(signal_name))
|
||||
{
|
||||
resource->connect(signal_name, Callable(target, signal_name));
|
||||
}
|
||||
}
|
||||
|
||||
const StringName &axis_name = axis->get_axis_name();
|
||||
const float &deadzone = axis->get_deadzone();
|
||||
if (axis_dimensions >= InputAxis::AXIS_DIMENSIONS::ONE)
|
||||
{
|
||||
const StringName &left_axis_action = axis_name + INPUTAXIS_LEFT_SUFFIX;
|
||||
const StringName &right_axis_action = axis_name + INPUTAXIS_RIGHT_SUFFIX;
|
||||
if (!input_map->has_action(left_axis_action)) input_map->add_action(left_axis_action, deadzone);
|
||||
if (!input_map->has_action(right_axis_action)) input_map->add_action(right_axis_action, deadzone);
|
||||
|
||||
const TypedArray<InputAxis2D> &left_right_events = axis->get_left_right_events();
|
||||
for(uint8_t e = 0; e < left_right_events.size(); e++)
|
||||
{
|
||||
const Ref<InputAxis2D> &left_right_event = left_right_events[e];
|
||||
input_map->action_add_event(left_axis_action, left_right_event->get_negative());
|
||||
input_map->action_add_event(right_axis_action, left_right_event->get_positive());
|
||||
}
|
||||
}
|
||||
|
||||
if (axis_dimensions >= InputAxis::AXIS_DIMENSIONS::TWO)
|
||||
{
|
||||
const StringName &back_axis_action = axis_name + INPUTAXIS_BACK_SUFFIX;
|
||||
const StringName &forward_axis_action = axis_name + INPUTAXIS_FORWARD_SUFFIX;
|
||||
if (!input_map->has_action(back_axis_action)) input_map->add_action(back_axis_action, deadzone);
|
||||
if (!input_map->has_action(forward_axis_action)) input_map->add_action(forward_axis_action, deadzone);
|
||||
|
||||
const TypedArray<InputAxis2D> &forward_back_events = axis->get_forward_back_events();
|
||||
for(uint8_t e = 0; e < forward_back_events.size(); e++)
|
||||
{
|
||||
const Ref<InputAxis2D> &forward_back_event = forward_back_events[e];
|
||||
input_map->action_add_event(back_axis_action, forward_back_event->get_negative());
|
||||
input_map->action_add_event(forward_axis_action, forward_back_event->get_positive());
|
||||
}
|
||||
}
|
||||
|
||||
if (axis_dimensions >= InputAxis::AXIS_DIMENSIONS::THREE)
|
||||
{
|
||||
const StringName &down_axis_action = axis_name + INPUTAXIS_DOWN_SUFFIX;
|
||||
const StringName &up_axis_action = axis_name + INPUTAXIS_UP_SUFFIX;
|
||||
if (!input_map->has_action(down_axis_action)) input_map->add_action(down_axis_action, deadzone);
|
||||
if (!input_map->has_action(up_axis_action)) input_map->add_action(up_axis_action, deadzone);
|
||||
|
||||
const TypedArray<InputAxis2D> &up_down_events = axis->get_up_down_events();
|
||||
for(uint8_t e = 0; e < up_down_events.size(); e++)
|
||||
{
|
||||
const Ref<InputAxis2D> &up_down_event = up_down_events[e];
|
||||
input_map->action_add_event(down_axis_action, up_down_event->get_negative());
|
||||
input_map->action_add_event(up_axis_action, up_down_event->get_positive());
|
||||
}
|
||||
}
|
||||
|
||||
if (axis->get_include_mouse())
|
||||
{
|
||||
Engine *engine = Engine::get_singleton();
|
||||
this->call_deferred("_enable_mouse_capture");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this->active_resources.insert(resource);
|
||||
|
||||
const StringName input_resource_method = target->get_name() + StringName("_") + resource->get_path();
|
||||
if (this->bound_remove_methods.find(input_resource_method) == this->bound_remove_methods.end())
|
||||
{
|
||||
MethodBind *bind = ClassDB::bind_method(input_resource_method, &InputHandler::remove_input_resource);
|
||||
this->bound_remove_methods.emplace(input_resource_method, bind);
|
||||
}
|
||||
target->connect("tree_exiting", Callable(this, input_resource_method).bind(target, resource), CONNECT_ONE_SHOT);
|
||||
}
|
||||
void InputHandler::_enable_mouse_capture()
|
||||
{
|
||||
Input *input = Input::get_singleton();
|
||||
input->set_mouse_mode(Input::MOUSE_MODE_CAPTURED);
|
||||
}
|
||||
|
||||
void InputHandler::remove_input_resource(Node *target, InputResource *resource)
|
||||
{
|
||||
InputMap *input_map = InputMap::get_singleton();
|
||||
const TypedArray<InputAction> &input_actions = resource->get_input_actions();
|
||||
const TypedArray<InputAxis> &input_axes = resource->get_input_axes();
|
||||
|
||||
for(uint8_t i = 0; i < input_actions.size(); i++)
|
||||
{
|
||||
if (const InputAction *action = cast_to<InputAction>(input_actions[i]))
|
||||
{
|
||||
input_map->action_erase_events(action->get_action_name());
|
||||
for(uint8_t s = 0; s < 2; s++)
|
||||
{
|
||||
const StringName signal_name = StringName("_on_") + action->get_action_name() + input_action_suffixes[s];
|
||||
if (target->has_method(signal_name))
|
||||
{
|
||||
resource->disconnect(signal_name, Callable(target, signal_name));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for(uint8_t i = 0; i < input_axes.size(); i++)
|
||||
{
|
||||
if (const InputAxis *axis = cast_to<InputAxis>(input_axes[i]))
|
||||
{
|
||||
for(uint8_t s = 0; s < 3; s++)
|
||||
{
|
||||
const StringName signal_name = StringName("_on_") + axis->get_axis_name() + this->input_axis_suffixes[s];
|
||||
if (target->has_method(signal_name))
|
||||
{
|
||||
resource->disconnect(signal_name, Callable(target, signal_name));
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* This part would be a good, safe precaution to take, but it seems
|
||||
* to cause inputs to stick if an input resource is removed while
|
||||
* an axis is being used. Removing this seems to have no negative
|
||||
* side effects, but I'm keeping it commented here in case there
|
||||
* turns out to be a reason to put it back later.
|
||||
*/
|
||||
// const StringName &axis_name = axis->get_axis_name();
|
||||
// const uint8_t &axis_dimensions = axis->get_axis_dimensions();
|
||||
// const float &deadzone = axis->get_deadzone();
|
||||
// if (axis_dimensions >= InputAxis::AXIS_DIMENSIONS::ONE)
|
||||
// {
|
||||
// const StringName &left_axis_action = axis_name + INPUTAXIS_LEFT_SUFFIX;
|
||||
// const StringName &right_axis_action = axis_name + INPUTAXIS_RIGHT_SUFFIX;
|
||||
|
||||
// input_map->action_erase_events(left_axis_action);
|
||||
// input_map->action_erase_events(right_axis_action);
|
||||
// }
|
||||
|
||||
// if (axis_dimensions >= InputAxis::AXIS_DIMENSIONS::TWO)
|
||||
// {
|
||||
// const StringName &back_axis_action = axis_name + INPUTAXIS_BACK_SUFFIX;
|
||||
// const StringName &forward_axis_action = axis_name + INPUTAXIS_FORWARD_SUFFIX;
|
||||
|
||||
// input_map->action_erase_events(back_axis_action);
|
||||
// input_map->action_erase_events(forward_axis_action);
|
||||
// }
|
||||
|
||||
// if (axis_dimensions >= InputAxis::AXIS_DIMENSIONS::THREE)
|
||||
// {
|
||||
// const StringName &down_axis_action = axis_name + INPUTAXIS_DOWN_SUFFIX;
|
||||
// const StringName &up_axis_action = axis_name + INPUTAXIS_UP_SUFFIX;
|
||||
|
||||
// input_map->action_erase_events(down_axis_action);
|
||||
// input_map->action_erase_events(up_axis_action);
|
||||
// }
|
||||
|
||||
if (axis->get_include_mouse())
|
||||
{
|
||||
Engine *engine = Engine::get_singleton();
|
||||
this->call_deferred("_disable_mouse_capture");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this->active_resources.erase(resource);
|
||||
}
|
||||
void InputHandler::_disable_mouse_capture()
|
||||
{
|
||||
Input *input = Input::get_singleton();
|
||||
input->set_mouse_mode(Input::MOUSE_MODE_VISIBLE);
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* ©2023 Batty Bovine Productions, LLC. All Rights Reserved.
|
||||
*
|
||||
* If this source code makes it to the public internet, this software can be
|
||||
* considered to be protected by the MIT licence. Have fun with it.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "orng_macros.h"
|
||||
#include "singletons/singleton_interface.h"
|
||||
|
||||
#include <map>
|
||||
#include <unordered_set>
|
||||
|
||||
#include "resources/input_resource.h"
|
||||
|
||||
|
||||
class InputHandler : public Node, SingletonInterface
|
||||
{
|
||||
GDCLASS(InputHandler, Node);
|
||||
GDSINGLETON(InputHandler);
|
||||
|
||||
public:
|
||||
virtual void _process(double delta) override;
|
||||
virtual void _input(const Ref<InputEvent> &event) override;
|
||||
|
||||
void add_input_resource(Node *target, InputResource *resource);
|
||||
void remove_input_resource(Node *target, InputResource *resource);
|
||||
|
||||
protected:
|
||||
std::unordered_set<InputResource*> active_resources;
|
||||
|
||||
private:
|
||||
void _enable_mouse_capture();
|
||||
void _disable_mouse_capture();
|
||||
|
||||
const StringName input_action_suffixes[2] = { INPUTACTION_SUFFIX_PRESSED, INPUTACTION_SUFFIX_RELEASED };
|
||||
const StringName input_axis_suffixes[3] = { INPUTAXIS_ONE_DIMENSIONAL_SUFFIX, INPUTAXIS_TWO_DIMENSIONAL_SUFFIX, INPUTAXIS_THREE_DIMENSIONAL_SUFFIX };
|
||||
|
||||
std::map<const StringName, MethodBind*> bound_remove_methods;
|
||||
|
||||
// Godot boilerplate below
|
||||
protected:
|
||||
static void _bind_methods()
|
||||
{
|
||||
GDSINGLETON_BIND(InputHandler);
|
||||
|
||||
{
|
||||
BIND_METHOD_2PARAM(InputHandler, add_input_resource, target, resource);
|
||||
BIND_METHOD_2PARAM(InputHandler, remove_input_resource, target, resource);
|
||||
|
||||
BIND_METHOD(InputHandler, _enable_mouse_capture);
|
||||
BIND_METHOD(InputHandler, _disable_mouse_capture);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,168 @@
|
||||
/*
|
||||
* ©2023 Batty Bovine Productions, LLC. All Rights Reserved.
|
||||
*
|
||||
* If this source code makes it to the public internet, this software can be
|
||||
* considered to be protected by the MIT licence. Have fun with it.
|
||||
*/
|
||||
|
||||
#include "save_manager.h"
|
||||
#include "nodes/player_spawn.h"
|
||||
|
||||
#include <godot_cpp/classes/dir_access.hpp>
|
||||
#include <godot_cpp/classes/engine.hpp>
|
||||
#include <godot_cpp/classes/file_access.hpp>
|
||||
#include <godot_cpp/classes/json.hpp>
|
||||
#include <godot_cpp/classes/scene_tree.hpp>
|
||||
#include <godot_cpp/classes/window.hpp>
|
||||
#include <godot_cpp/variant/utility_functions.hpp>
|
||||
using namespace godot;
|
||||
|
||||
GDSINGLETON_CPP(SaveManager);
|
||||
|
||||
|
||||
SaveManager::SaveManager()
|
||||
{
|
||||
this->save_file_data = memnew(SaveFileData);
|
||||
this->save_file_index = memnew(SaveFileIndex);
|
||||
this->read_save_index_data();
|
||||
}
|
||||
|
||||
SaveManager::~SaveManager()
|
||||
{
|
||||
if (this->save_file_index) memdelete(this->save_file_index);
|
||||
if (this->save_file_data) memdelete(this->save_file_data);
|
||||
}
|
||||
|
||||
|
||||
void SaveManager::set_new_level(StringName level)
|
||||
{
|
||||
this->save_file_data->level = level;
|
||||
this->set_new_checkpoint(PLAYER_ORIGIN_TAG);
|
||||
}
|
||||
|
||||
void SaveManager::set_new_checkpoint(StringName checkpoint_name)
|
||||
{
|
||||
if(this->save_file_data->checkpoint != checkpoint_name)
|
||||
{
|
||||
this->save_file_data->checkpoint = checkpoint_name;
|
||||
this->write_save_file_data();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void SaveManager::create_new_save_file_data(uint8_t slot, SaveFileData::DifficultySetting difficulty)
|
||||
{
|
||||
if (this->save_file_data)
|
||||
{
|
||||
memdelete(this->save_file_data);
|
||||
}
|
||||
this->save_file_data = memnew(SaveFileData);
|
||||
this->save_file_data->init();
|
||||
|
||||
this->current_slot = slot;
|
||||
this->current_difficulty = difficulty;
|
||||
this->save_file_index->clear_slot(slot);
|
||||
}
|
||||
|
||||
void SaveManager::read_save_file_data(uint8_t slot)
|
||||
{
|
||||
#ifdef DEBUG_ENABLED
|
||||
Ref<FileAccess> save_file = FileAccess::open(SaveManager::build_slot_name(slot) + StringName(SAVE_FILE_DEBUG_SUFFIX), FileAccess::READ);
|
||||
#else
|
||||
Ref<FileAccess> save_file = FileAccess::open_compressed(SaveManager::build_slot_name(slot), FileAccess::READ, FileAccess::COMPRESSION_GZIP);
|
||||
#endif
|
||||
if(save_file.is_valid())
|
||||
{
|
||||
#ifdef DEBUG_ENABLED
|
||||
const Dictionary &dictionary = JSON::parse_string(save_file->get_as_text());
|
||||
#else
|
||||
const Dictionary &dictionary = save_file->get_var();
|
||||
#endif
|
||||
this->save_file_data->deserialize(dictionary);
|
||||
}
|
||||
|
||||
this->read_save_index_data();
|
||||
}
|
||||
void SaveManager::read_save_index_data()
|
||||
{
|
||||
#ifdef DEBUG_ENABLED
|
||||
Ref<FileAccess> save_index = FileAccess::open(SaveManager::build_index_name() + StringName(SAVE_FILE_DEBUG_SUFFIX), FileAccess::READ);
|
||||
#else
|
||||
Ref<FileAccess> save_index = FileAccess::open_compressed(SaveManager::build_index_name(), FileAccess::READ, FileAccess::COMPRESSION_GZIP);
|
||||
#endif
|
||||
if(save_index.is_valid())
|
||||
{
|
||||
#ifdef DEBUG_ENABLED
|
||||
const Dictionary &dictionary = JSON::parse_string(save_index->get_as_text());
|
||||
#else
|
||||
const Dictionary &dictionary = save_index->get_var();
|
||||
#endif
|
||||
this->save_file_index->deserialize(dictionary);
|
||||
}
|
||||
}
|
||||
|
||||
void SaveManager::write_save_file_data()
|
||||
{
|
||||
const Dictionary &dictionary = this->save_file_data->serialize();
|
||||
DirAccess::make_dir_absolute(SAVE_FOLDER);
|
||||
#ifdef DEBUG_ENABLED
|
||||
Ref<FileAccess> save_file = FileAccess::open(SaveManager::build_slot_name(this->current_slot) + StringName(SAVE_FILE_DEBUG_SUFFIX), FileAccess::WRITE);
|
||||
#else
|
||||
Ref<FileAccess> save_file = FileAccess::open_compressed(SaveManager::build_slot_name(this->current_slot), FileAccess::WRITE, FileAccess::COMPRESSION_GZIP);
|
||||
#endif
|
||||
if(save_file.is_valid())
|
||||
{
|
||||
#ifdef DEBUG_ENABLED
|
||||
save_file->store_string(JSON::stringify(dictionary));
|
||||
#else
|
||||
save_file->store_var(dictionary);
|
||||
#endif
|
||||
save_file->close();
|
||||
}
|
||||
|
||||
this->save_file_index->update_header(this->current_slot, *this->save_file_data);
|
||||
this->write_save_index_data();
|
||||
}
|
||||
void SaveManager::write_save_index_data()
|
||||
{
|
||||
const Dictionary &dictionary = this->save_file_index->serialize();
|
||||
DirAccess::make_dir_absolute(SAVE_FOLDER);
|
||||
#ifdef DEBUG_ENABLED
|
||||
Ref<FileAccess> save_index = FileAccess::open(SaveManager::build_index_name() + StringName(SAVE_FILE_DEBUG_SUFFIX), FileAccess::WRITE);
|
||||
#else
|
||||
Ref<FileAccess> save_index = FileAccess::open_compressed(SaveManager::build_index_name(), FileAccess::WRITE, FileAccess::COMPRESSION_GZIP);
|
||||
#endif
|
||||
if(save_index.is_valid())
|
||||
{
|
||||
#ifdef DEBUG_ENABLED
|
||||
save_index->store_string(JSON::stringify(dictionary));
|
||||
#else
|
||||
save_index->store_var(dictionary);
|
||||
#endif
|
||||
save_index->close();
|
||||
}
|
||||
}
|
||||
|
||||
void SaveManager::delete_save_file_data(uint8_t slot)
|
||||
{
|
||||
DirAccess::remove_absolute(SaveManager::build_slot_name(slot));
|
||||
this->save_file_index->clear_slot(slot);
|
||||
}
|
||||
|
||||
|
||||
bool SaveManager::does_save_file_exist(uint8_t slot) const
|
||||
{
|
||||
return FileAccess::file_exists(SaveManager::build_slot_name(slot));
|
||||
}
|
||||
|
||||
bool SaveManager::do_any_save_files_exist() const
|
||||
{
|
||||
for (std::pair<const uint8_t, std::unique_ptr<SaveHeaderData>> &header : this->save_file_index->headers)
|
||||
{
|
||||
if (FileAccess::file_exists(SaveManager::build_slot_name(header.first)))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
/*
|
||||
* ©2023 Batty Bovine Productions, LLC. All Rights Reserved.
|
||||
*
|
||||
* If this source code makes it to the public internet, this software can be
|
||||
* considered to be protected by the MIT licence. Have fun with it.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "singleton_interface.h"
|
||||
#include "orng_macros.h"
|
||||
|
||||
#include "data_assets/save_file_data.h"
|
||||
|
||||
#include <godot_cpp/classes/node.hpp>
|
||||
using namespace godot;
|
||||
|
||||
#define SAVE_FOLDER "user://save_data/"
|
||||
#define SAVE_FILE_PREFIX "save_file_"
|
||||
#define SAVE_HEADER_INDEX_NAME "save_index"
|
||||
#define SAVE_FILE_EXTENSION ".sav"
|
||||
#define SAVE_FILE_DEBUG_SUFFIX "-debug"
|
||||
|
||||
|
||||
class SaveManager : public Node, public SingletonInterface
|
||||
{
|
||||
GDCLASS(SaveManager, Node);
|
||||
GDSINGLETON(SaveManager);
|
||||
|
||||
public:
|
||||
SaveManager();
|
||||
~SaveManager();
|
||||
|
||||
void create_new_save_file_data(uint8_t slot, SaveFileData::DifficultySetting difficulty);
|
||||
|
||||
void read_save_file_data(uint8_t slot);
|
||||
void read_save_index_data();
|
||||
|
||||
void write_save_file_data();
|
||||
void write_save_index_data();
|
||||
|
||||
void delete_save_file_data(uint8_t slot);
|
||||
|
||||
bool does_save_file_exist(uint8_t slot) const;
|
||||
bool do_any_save_files_exist() const;
|
||||
|
||||
SaveHeaderData *get_save_file_header(uint8_t slot) const { return this->save_file_index->headers[slot].get(); }
|
||||
SaveFileData *get_save_file_data() const { return this->save_file_data; }
|
||||
void set_new_level(StringName level);
|
||||
void set_new_checkpoint(StringName checkpoint_name);
|
||||
|
||||
StringName get_current_level() const { return this->save_file_data ? this->save_file_data->level : ""; }
|
||||
StringName get_current_checkpoint() const { return this->save_file_data ? this->save_file_data->checkpoint : ""; }
|
||||
|
||||
static const StringName build_slot_name(const uint8_t slot_number) { return StringName(SAVE_FOLDER) + StringName(SAVE_FILE_PREFIX) + itos(slot_number) + StringName(SAVE_FILE_EXTENSION); }
|
||||
static const StringName build_index_name() { return StringName(SAVE_FOLDER) + StringName(SAVE_HEADER_INDEX_NAME) + StringName(SAVE_FILE_EXTENSION); }
|
||||
|
||||
private:
|
||||
SaveFileData *save_file_data = nullptr;
|
||||
SaveFileIndex *save_file_index = nullptr;
|
||||
uint8_t current_slot = 0;
|
||||
SaveFileData::DifficultySetting current_difficulty = SaveFileData::DifficultySetting::NORMAL;
|
||||
int32_t user_index = 0;
|
||||
|
||||
// Godot boilerplate below
|
||||
protected:
|
||||
static void _bind_methods()
|
||||
{
|
||||
{
|
||||
GDSINGLETON_BIND(SaveManager);
|
||||
|
||||
{
|
||||
BIND_METHOD_2PARAM(SaveManager, create_new_save_file_data, slot, difficulty);
|
||||
BIND_METHOD_1PARAM(SaveManager, read_save_file_data, slot);
|
||||
BIND_METHOD(SaveManager, write_save_file_data);
|
||||
BIND_METHOD_1PARAM(SaveManager, delete_save_file_data, slot);
|
||||
|
||||
BIND_METHOD_1PARAM(SaveManager, does_save_file_exist, slot);
|
||||
BIND_METHOD(SaveManager, do_any_save_files_exist);
|
||||
|
||||
BIND_METHOD(SaveManager, get_current_level);
|
||||
BIND_METHOD(SaveManager, get_current_checkpoint);
|
||||
|
||||
ADD_SIGNAL(MethodInfo("save_file_loaded", PropertyInfo(Variant::INT, "slot"), PropertyInfo(Variant::OBJECT, "save_header", PROPERTY_HINT_RESOURCE_TYPE, "SaveHeaderData"), PropertyInfo(Variant::BOOL, "success")));
|
||||
ADD_SIGNAL(MethodInfo("save_file_saved", PropertyInfo(Variant::INT, "slot"), PropertyInfo(Variant::OBJECT, "save_header", PROPERTY_HINT_RESOURCE_TYPE, "SaveHeaderData"), PropertyInfo(Variant::BOOL, "success")));
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,220 @@
|
||||
/*
|
||||
* ©2024 Batty Bovine Productions, LLC. All Rights Reserved.
|
||||
*
|
||||
* If this source code makes it to the public internet, this software can be
|
||||
* considered to be protected by the MIT licence. Have fun with it.
|
||||
*/
|
||||
|
||||
#include "scene_loader.h"
|
||||
|
||||
#include "orng_macros.h"
|
||||
#include "singletons/save_manager.h"
|
||||
#include "singletons/vendor_service/vendor_service.h"
|
||||
|
||||
#include <godot_cpp/classes/engine.hpp>
|
||||
#include <godot_cpp/classes/packed_scene.hpp>
|
||||
#include <godot_cpp/classes/scene_tree.hpp>
|
||||
#include <godot_cpp/classes/scene_tree_timer.hpp>
|
||||
#include <godot_cpp/classes/window.hpp>
|
||||
#include <godot_cpp/variant/utility_functions.hpp>
|
||||
using namespace godot;
|
||||
|
||||
GDSINGLETON_CPP(SceneLoader)
|
||||
|
||||
|
||||
SceneLoader::SceneLoader()
|
||||
{
|
||||
this->enable_load_screen = true;
|
||||
}
|
||||
|
||||
void SceneLoader::_enter_tree()
|
||||
{
|
||||
this->resource_loader = ResourceLoader::get_singleton();
|
||||
|
||||
Ref<PackedScene> loading_screen = this->resource_loader->load("uid://d0d7k8ebwixgp");
|
||||
this->_loading_screen_instance = loading_screen->instantiate();
|
||||
this->_loading_screen_instance->connect("load_ready", callable_mp(this, &SceneLoader::_loading_screen_initialised));
|
||||
this->_loading_screen_instance->connect("beginning_fade_out", callable_mp(this, &SceneLoader::_finish_loading_next_level));
|
||||
this->_loading_screen_instance->connect("load_finished", callable_mp(this, &SceneLoader::_post_load_cleanup));
|
||||
}
|
||||
|
||||
|
||||
void SceneLoader::load_level(const StringName path)
|
||||
{
|
||||
this->enable_load_screen = true;
|
||||
this->_continue_load_level(path);
|
||||
}
|
||||
|
||||
void SceneLoader::load_level_without_load_screen(const StringName path)
|
||||
{
|
||||
this->enable_load_screen = false;
|
||||
this->_continue_load_level(path);
|
||||
}
|
||||
|
||||
void SceneLoader::load_save_file_level()
|
||||
{
|
||||
this->enable_load_screen = true;
|
||||
this->_continue_load_level(SaveManager::get_singleton()->get_current_level());
|
||||
}
|
||||
|
||||
|
||||
void SceneLoader::_continue_load_level(const StringName path)
|
||||
{
|
||||
this->_level_to_load = path;
|
||||
|
||||
if (this->enable_load_screen)
|
||||
this->_initialise_loading_screen();
|
||||
else
|
||||
this->_loading_screen_initialised();
|
||||
}
|
||||
|
||||
|
||||
void SceneLoader::_initialise_loading_screen()
|
||||
{
|
||||
if (!this->_loading_screen_instance->is_inside_tree())
|
||||
{
|
||||
this->get_tree()->get_root()->add_child(this->_loading_screen_instance);
|
||||
this->_loading_screen_instance->call("begin_fade_in");
|
||||
}
|
||||
}
|
||||
|
||||
void SceneLoader::_loading_screen_initialised()
|
||||
{
|
||||
Node *current_level = this->get_tree()->get_current_scene();
|
||||
if (current_level)
|
||||
{
|
||||
current_level->connect("tree_exited", callable_mp(this, &SceneLoader::_previous_level_deleted), ConnectFlags::CONNECT_ONE_SHOT);
|
||||
current_level->queue_free();
|
||||
current_level = nullptr;
|
||||
}
|
||||
else
|
||||
{
|
||||
this->_previous_level_deleted();
|
||||
}
|
||||
}
|
||||
void SceneLoader::_previous_level_deleted()
|
||||
{
|
||||
if (this->resource_loader->load_threaded_request(this->_level_to_load, "PackedScene", true) != Error::OK)
|
||||
{
|
||||
UtilityFunctions::printerr("Scene ", this->_level_to_load, " does not exist.");
|
||||
return;
|
||||
}
|
||||
|
||||
CALL_NEXT_FRAME(callable_mp(this, &SceneLoader::_resource_load_recursive));
|
||||
}
|
||||
|
||||
void SceneLoader::_resource_load_recursive()
|
||||
{
|
||||
ResourceLoader::ThreadLoadStatus load_status = this->resource_loader->load_threaded_get_status(this->_level_to_load);
|
||||
|
||||
switch (load_status)
|
||||
{
|
||||
case ResourceLoader::ThreadLoadStatus::THREAD_LOAD_LOADED:
|
||||
if (this->enable_load_screen)
|
||||
CALL_NEXT_FRAME(callable_mp(this, &SceneLoader::_begin_load_screen_fade_out));
|
||||
else
|
||||
CALL_NEXT_FRAME(callable_mp(this, &SceneLoader::_finish_loading_next_level));
|
||||
return;
|
||||
|
||||
case ResourceLoader::ThreadLoadStatus::THREAD_LOAD_INVALID_RESOURCE:
|
||||
UtilityFunctions::printerr(this->_level_to_load, " is not a valid resource.");
|
||||
return;
|
||||
case ResourceLoader::ThreadLoadStatus::THREAD_LOAD_FAILED:
|
||||
UtilityFunctions::printerr("Can not load ", this->_level_to_load, " for an unknown reason.");
|
||||
return;
|
||||
|
||||
case ResourceLoader::ThreadLoadStatus::THREAD_LOAD_IN_PROGRESS:
|
||||
break;
|
||||
}
|
||||
|
||||
CALL_NEXT_FRAME(callable_mp(this, &SceneLoader::_resource_load_recursive));
|
||||
}
|
||||
|
||||
void SceneLoader::_begin_load_screen_fade_out()
|
||||
{
|
||||
if (this->enable_load_screen)
|
||||
this->_loading_screen_instance->call("begin_fade_out");
|
||||
}
|
||||
|
||||
void SceneLoader::_finish_loading_next_level()
|
||||
{
|
||||
VendorService *vendor_service = VendorService::get_singleton();
|
||||
if (!vendor_service->is_game_ownership_valid())
|
||||
{
|
||||
if (this->enable_load_screen && UtilityFunctions::randf() < 0.2f)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Ref<PackedScene> next_scene_resource = this->resource_loader->load_threaded_get(this->_level_to_load);
|
||||
if (next_scene_resource.is_valid())
|
||||
{
|
||||
this->get_tree()->change_scene_to_packed(next_scene_resource);
|
||||
}
|
||||
}
|
||||
|
||||
void SceneLoader::_post_load_cleanup()
|
||||
{
|
||||
this->get_tree()->get_root()->remove_child(this->_loading_screen_instance);
|
||||
}
|
||||
|
||||
|
||||
void SceneLoader::load_scene(const StringName path, Callable callback)
|
||||
{
|
||||
if (path.is_empty())
|
||||
return;
|
||||
|
||||
if (this->resource_loader->load_threaded_request(path, "PackedScene", true) != Error::OK)
|
||||
{
|
||||
UtilityFunctions::printerr("Scene ", path, " does not exist.");
|
||||
return;
|
||||
}
|
||||
|
||||
this->_scenes_to_load.emplace(path, callback);
|
||||
|
||||
if (this->_scenes_loading == false)
|
||||
{
|
||||
this->_scenes_loading = true;
|
||||
CALL_NEXT_FRAME(callable_mp(this, &SceneLoader::_resource_load_scene_recursive));
|
||||
}
|
||||
}
|
||||
|
||||
void SceneLoader::_resource_load_scene_recursive()
|
||||
{
|
||||
std::vector<std::pair<StringName, Callable>> finished_scenes;
|
||||
|
||||
for (const std::pair<StringName, Callable> &scene : this->_scenes_to_load)
|
||||
{
|
||||
ResourceLoader::ThreadLoadStatus load_status = this->resource_loader->load_threaded_get_status(scene.first);
|
||||
|
||||
switch (load_status)
|
||||
{
|
||||
case ResourceLoader::ThreadLoadStatus::THREAD_LOAD_LOADED:
|
||||
finished_scenes.push_back(scene);
|
||||
this->_scenes_to_load.erase(scene.first);
|
||||
break;
|
||||
|
||||
case ResourceLoader::ThreadLoadStatus::THREAD_LOAD_INVALID_RESOURCE:
|
||||
UtilityFunctions::printerr(scene.first, " is not a valid resource.");
|
||||
return;
|
||||
case ResourceLoader::ThreadLoadStatus::THREAD_LOAD_FAILED:
|
||||
UtilityFunctions::printerr("Can not load ", scene.first, " for an unknown reason.");
|
||||
return;
|
||||
|
||||
case ResourceLoader::ThreadLoadStatus::THREAD_LOAD_IN_PROGRESS:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for (const std::pair<StringName, Callable> &scene : finished_scenes)
|
||||
{
|
||||
Ref<PackedScene> next_scene_resource = this->resource_loader->load_threaded_get(scene.first);
|
||||
scene.second.call_deferred(next_scene_resource);
|
||||
}
|
||||
|
||||
if (this->_scenes_to_load.empty())
|
||||
this->_scenes_loading = false;
|
||||
else
|
||||
CALL_NEXT_FRAME(callable_mp(this, &SceneLoader::_resource_load_scene_recursive));
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* ©2024 Batty Bovine Productions, LLC. All Rights Reserved.
|
||||
*
|
||||
* If this source code makes it to the public internet, this software can be
|
||||
* considered to be protected by the MIT licence. Have fun with it.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "singleton_interface.h"
|
||||
#include "orng_macros.h"
|
||||
|
||||
#include <map>
|
||||
|
||||
#include <godot_cpp/classes/node.hpp>
|
||||
#include <godot_cpp/classes/resource_loader.hpp>
|
||||
using namespace godot;
|
||||
|
||||
|
||||
class SceneLoader : public Node, public SingletonInterface
|
||||
{
|
||||
GDCLASS(SceneLoader, Node);
|
||||
GDSINGLETON(SceneLoader);
|
||||
|
||||
public:
|
||||
SceneLoader();
|
||||
virtual void _enter_tree() override;
|
||||
|
||||
void load_level(const StringName path);
|
||||
void load_level_without_load_screen(const StringName path);
|
||||
void load_save_file_level();
|
||||
|
||||
void load_scene(const StringName path, Callable callback);
|
||||
|
||||
private:
|
||||
void _continue_load_level(const StringName path);
|
||||
void _initialise_loading_screen();
|
||||
void _loading_screen_initialised();
|
||||
void _previous_level_deleted();
|
||||
void _resource_load_recursive();
|
||||
void _begin_load_screen_fade_out();
|
||||
void _finish_loading_next_level();
|
||||
void _post_load_cleanup();
|
||||
|
||||
void _resource_load_scene_recursive();
|
||||
|
||||
uint8_t enable_load_screen : 1;
|
||||
|
||||
ResourceLoader *resource_loader = nullptr;
|
||||
Node *_loading_screen_instance = nullptr;
|
||||
StringName _level_to_load;
|
||||
|
||||
std::map<StringName, Callable> _scenes_to_load;
|
||||
bool _scenes_loading = false;
|
||||
|
||||
// Godot boilerplate below
|
||||
protected:
|
||||
static void _bind_methods()
|
||||
{
|
||||
GDSINGLETON_BIND(SceneLoader);
|
||||
|
||||
// public:
|
||||
BIND_METHOD_1PARAM(SceneLoader, load_level, path);
|
||||
BIND_METHOD_1PARAM(SceneLoader, load_level_without_load_screen, path);
|
||||
BIND_METHOD(SceneLoader, load_save_file_level);
|
||||
|
||||
// private:
|
||||
BIND_METHOD(SceneLoader, _loading_screen_initialised);
|
||||
BIND_METHOD(SceneLoader, _resource_load_recursive);
|
||||
BIND_METHOD(SceneLoader, _begin_load_screen_fade_out);
|
||||
BIND_METHOD(SceneLoader, _finish_loading_next_level);
|
||||
BIND_METHOD(SceneLoader, _post_load_cleanup);
|
||||
BIND_METHOD(SceneLoader, _resource_load_scene_recursive);
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,98 @@
|
||||
/*
|
||||
* ©2023 Batty Bovine Productions, LLC. All Rights Reserved.
|
||||
*
|
||||
* If this source code makes it to the public internet, this software can be
|
||||
* considered to be protected by the MIT licence. Have fun with it.
|
||||
*/
|
||||
|
||||
#ifndef _SINGLETON_INTERFACE_H__
|
||||
#define _SINGLETON_INTERFACE_H__
|
||||
|
||||
#include <godot_cpp/classes/node.hpp>
|
||||
using namespace godot;
|
||||
|
||||
|
||||
/**
|
||||
* Define the global pointer used to store the singleton instance used by the
|
||||
* module functions. Place this below `using namespace godot;` in the
|
||||
* register_types.cpp file.
|
||||
*/
|
||||
#define GDSINGLETON_REGISTER_PTR(Class, Pointer) Class *Pointer = nullptr
|
||||
|
||||
/**
|
||||
* Register the singleton with the engine and perform initialisation. Place
|
||||
* this inside the `initialize_*_module()` function.
|
||||
*/
|
||||
#define GDSINGLETON_REGISTER_CLASS(Class, Pointer) \
|
||||
ClassDB::register_class<Class>(); \
|
||||
Pointer = memnew(Class); Pointer->init();
|
||||
|
||||
/**
|
||||
* Unregister the singleton with the engine and perform uninitialisation. Place
|
||||
* this inside the `uninitialize_*_module()` function.
|
||||
*/
|
||||
#define GDSINGLETON_UNREGISTER_CLASS(Pointer) \
|
||||
if (UtilityFunctions::is_instance_valid(Pointer)) { \
|
||||
Pointer->uninit(); \
|
||||
memdelete(Pointer); Pointer = nullptr; }
|
||||
|
||||
/**
|
||||
* Used in classes that derive from SingletonInterface. Place just below the
|
||||
* GDCLASS() macro in the header definition, and ensure the class derives
|
||||
* from SingletonInterface.
|
||||
*/
|
||||
#define GDSINGLETON(Class) \
|
||||
private: \
|
||||
virtual void _finish_init() override; \
|
||||
protected: \
|
||||
static Class *singleton; \
|
||||
public: \
|
||||
virtual void init() override; \
|
||||
virtual void uninit() override; \
|
||||
static Class \
|
||||
*get_singleton() \
|
||||
{ return singleton; } \
|
||||
|
||||
/**
|
||||
* Used in the class CPP file to set up the init and uninit functions and
|
||||
* handle registration with the engine. Place below `using namespace godot;`.
|
||||
*/
|
||||
#define GDSINGLETON_CPP(Class) \
|
||||
Class *Class::singleton = nullptr; \
|
||||
void Class::init() { \
|
||||
if (!Class::singleton) { \
|
||||
Class::singleton = this; \
|
||||
Engine *engine = Engine::get_singleton(); \
|
||||
if(engine->has_singleton(#Class)) { \
|
||||
engine->unregister_singleton(#Class); } \
|
||||
engine->register_singleton(#Class, this); \
|
||||
this->call_deferred("_finish_init"); }} \
|
||||
void Class::_finish_init() { \
|
||||
Engine *engine = Engine::get_singleton(); \
|
||||
Window *tree_root = Object::cast_to<SceneTree>( \
|
||||
engine->get_main_loop())->get_root(); \
|
||||
tree_root->add_child(this); } \
|
||||
void Class::uninit() { \
|
||||
singleton = nullptr; \
|
||||
Engine *engine = Engine::get_singleton(); \
|
||||
if (engine->has_singleton(#Class)) { \
|
||||
engine->unregister_singleton(#Class); } \
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to bind methods used by SingletonInterface to call deferred init code.
|
||||
* Place this in the _bind_methods() function.
|
||||
*/
|
||||
#define GDSINGLETON_BIND(Class) ClassDB::bind_method(D_METHOD("_finish_init"), &Class::_finish_init)
|
||||
|
||||
|
||||
class SingletonInterface
|
||||
{
|
||||
public:
|
||||
virtual void init() = 0;
|
||||
virtual void uninit() = 0;
|
||||
private:
|
||||
virtual void _finish_init() = 0;
|
||||
};
|
||||
|
||||
#endif // _SINGLETON_INTERFACE_H__
|
||||
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* ©2024 Batty Bovine Productions, LLC. All Rights Reserved.
|
||||
*
|
||||
* If this source code makes it to the public internet, this software can be
|
||||
* considered to be protected by the MIT licence. Have fun with it.
|
||||
*/
|
||||
|
||||
#include "singletons/vendor_service/vendor_service.h"
|
||||
|
||||
#include <godot_cpp/classes/engine.hpp>
|
||||
#include <godot_cpp/classes/packed_scene.hpp>
|
||||
#include <godot_cpp/classes/project_settings.hpp>
|
||||
#include <godot_cpp/classes/scene_tree.hpp>
|
||||
#include <godot_cpp/classes/scene_tree_timer.hpp>
|
||||
#include <godot_cpp/classes/window.hpp>
|
||||
#include <godot_cpp/variant/utility_functions.hpp>
|
||||
using namespace godot;
|
||||
|
||||
GDSINGLETON_CPP(VendorService);
|
||||
|
||||
|
||||
void VendorService::_enter_tree()
|
||||
{
|
||||
Engine *engine = Engine::get_singleton();
|
||||
#ifdef DEBUG_ENABLED
|
||||
if (!engine->is_editor_hint())
|
||||
{
|
||||
#endif
|
||||
if (this->steam_service = engine->get_singleton("Steam"))
|
||||
{
|
||||
const int steam_appid = STEAM_APP_ID;
|
||||
|
||||
this->steam_service->call("steamInitEx", false, steam_appid);
|
||||
this->userid = this->steam_service->call("getSteamID");
|
||||
this->username = this->steam_service->call("getPersonaName");
|
||||
}
|
||||
#ifdef DEBUG_ENABLED
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
bool VendorService::is_game_ownership_valid()
|
||||
{
|
||||
if (this->steam_service)
|
||||
{
|
||||
int ownership_proof = 0;
|
||||
ownership_proof += this->steam_service->call("isSubscribed") ? 1 : 0;
|
||||
ownership_proof += this->steam_service->call("isSubscribedFromFamilySharing") ? 1 : 0;
|
||||
ownership_proof += this->steam_service->call("isSubscribedFromFreeWeekend") ? 1 : 0;
|
||||
|
||||
Dictionary timed_trial_values = this->steam_service->call("isTimedTrial");
|
||||
if (!timed_trial_values.is_empty())
|
||||
ownership_proof += (uint32_t)UtilityFunctions::clamp(uint32_t(timed_trial_values["seconds_allowed"]) - uint32_t(timed_trial_values["seconds_played"]), 0, UINT32_MAX);
|
||||
|
||||
return ownership_proof;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* ©2024 Batty Bovine Productions, LLC. All Rights Reserved.
|
||||
*
|
||||
* If this source code makes it to the public internet, this software can be
|
||||
* considered to be protected by the MIT licence. Have fun with it.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "orng_macros.h"
|
||||
#include "singletons/singleton_interface.h"
|
||||
|
||||
#ifdef DEBUG_ENABLED
|
||||
#define STEAM_APP_ID ProjectSettings::get_singleton()->get_setting_with_override("orange_cat/vendor_service/steam/app_id")
|
||||
#else
|
||||
#define STEAM_APP_ID 480
|
||||
#endif
|
||||
|
||||
|
||||
class VendorService : public Node, public SingletonInterface
|
||||
{
|
||||
GDCLASS(VendorService, Node);
|
||||
GDSINGLETON(VendorService);
|
||||
|
||||
public:
|
||||
virtual void _enter_tree() override;
|
||||
|
||||
virtual bool is_game_ownership_valid();
|
||||
|
||||
private:
|
||||
Object *steam_service = nullptr;
|
||||
|
||||
int userid = 0;
|
||||
StringName username;
|
||||
|
||||
// Godot boilerplate below
|
||||
protected:
|
||||
static void _bind_methods()
|
||||
{
|
||||
GDSINGLETON_BIND(VendorService);
|
||||
|
||||
{
|
||||
// BIND_METHOD_2PARAM(SaveManager, create_new_save_file_data, slot, difficulty);
|
||||
// BIND_METHOD_1PARAM(SaveManager, read_save_file_data, slot);
|
||||
// BIND_METHOD(SaveManager, write_save_file_data);
|
||||
// BIND_METHOD_1PARAM(SaveManager, delete_save_file_data, slot);
|
||||
|
||||
// BIND_METHOD_1PARAM(SaveManager, does_save_file_exist, slot);
|
||||
// BIND_METHOD(SaveManager, do_any_save_files_exist);
|
||||
|
||||
// BIND_METHOD(SaveManager, get_current_level);
|
||||
// BIND_METHOD(SaveManager, get_current_checkpoint);
|
||||
|
||||
// ADD_SIGNAL(MethodInfo("save_file_loaded", PropertyInfo(Variant::INT, "slot"), PropertyInfo(Variant::OBJECT, "save_header", PROPERTY_HINT_RESOURCE_TYPE, "SaveHeaderData"), PropertyInfo(Variant::BOOL, "success")));
|
||||
// ADD_SIGNAL(MethodInfo("save_file_saved", PropertyInfo(Variant::INT, "slot"), PropertyInfo(Variant::OBJECT, "save_header", PROPERTY_HINT_RESOURCE_TYPE, "SaveHeaderData"), PropertyInfo(Variant::BOOL, "success")));
|
||||
}
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user