From 33a932ab0d3778c60d2b88a09e8e7f89c2b8f3ab Mon Sep 17 00:00:00 2001 From: Nemrav <> Date: Sun, 18 Aug 2024 13:48:30 -0300 Subject: [PATCH 1/9] initial cursor management --- .../src/openvic-extension/register_types.cpp | 9 + .../singletons/CursorSingleton.cpp | 515 ++++++++++++++++++ .../singletons/CursorSingleton.hpp | 97 ++++ .../singletons/SoundSingleton.cpp | 2 +- game/project.godot | 1 + game/src/Game/Autoload/CursorManager.gd | 211 +++++++ game/src/Game/GameStart.gd | 9 +- .../src/Game/MusicConductor/MusicConductor.gd | 5 +- 8 files changed, 845 insertions(+), 4 deletions(-) create mode 100644 extension/src/openvic-extension/singletons/CursorSingleton.cpp create mode 100644 extension/src/openvic-extension/singletons/CursorSingleton.hpp create mode 100644 game/src/Game/Autoload/CursorManager.gd diff --git a/extension/src/openvic-extension/register_types.cpp b/extension/src/openvic-extension/register_types.cpp index 0b9d7790..e102f762 100644 --- a/extension/src/openvic-extension/register_types.cpp +++ b/extension/src/openvic-extension/register_types.cpp @@ -13,6 +13,7 @@ #include "openvic-extension/classes/MapMesh.hpp" #include "openvic-extension/singletons/AssetManager.hpp" #include "openvic-extension/singletons/Checksum.hpp" +#include "openvic-extension/singletons/CursorSingleton.hpp" #include "openvic-extension/singletons/GameSingleton.hpp" #include "openvic-extension/singletons/LoadLocalisation.hpp" #include "openvic-extension/singletons/MenuSingleton.hpp" @@ -23,6 +24,7 @@ using namespace godot; using namespace OpenVic; static Checksum* _checksum_singleton = nullptr; +static CursorSingleton* _cursor_singleton = nullptr; static LoadLocalisation* _load_localisation = nullptr; static GameSingleton* _game_singleton = nullptr; static MenuSingleton* _menu_singleton = nullptr; @@ -39,6 +41,10 @@ void initialize_openvic_types(ModuleInitializationLevel p_level) { _checksum_singleton = memnew(Checksum); Engine::get_singleton()->register_singleton("Checksum", Checksum::get_singleton()); + ClassDB::register_class(); + _cursor_singleton = memnew(CursorSingleton); + Engine::get_singleton()->register_singleton("CursorSingleton", CursorSingleton::get_singleton()); + ClassDB::register_class(); _load_localisation = memnew(LoadLocalisation); Engine::get_singleton()->register_singleton("LoadLocalisation", LoadLocalisation::get_singleton()); @@ -89,6 +95,9 @@ void uninitialize_openvic_types(ModuleInitializationLevel p_level) { Engine::get_singleton()->unregister_singleton("Checksum"); memdelete(_checksum_singleton); + Engine::get_singleton()->unregister_singleton("CursorSingleton"); + memdelete(_cursor_singleton); + Engine::get_singleton()->unregister_singleton("LoadLocalisation"); memdelete(_load_localisation); diff --git a/extension/src/openvic-extension/singletons/CursorSingleton.cpp b/extension/src/openvic-extension/singletons/CursorSingleton.cpp new file mode 100644 index 00000000..eb20194f --- /dev/null +++ b/extension/src/openvic-extension/singletons/CursorSingleton.cpp @@ -0,0 +1,515 @@ +#include "CursorSingleton.hpp" + +#include + +#include +#include +#include +#include +#include + +#include +#include + +using OpenVic::Utilities::godot_to_std_string; +using OpenVic::Utilities::std_to_godot_string; + +using namespace godot; +using namespace OpenVic; +using namespace OpenVic::NodeTools; + +void CursorSingleton::_bind_methods() { + OV_BIND_METHOD(CursorSingleton::load_cursors); + OV_BIND_METHOD(CursorSingleton::get_image,"normal",0); + OV_BIND_METHOD(CursorSingleton::get_hotspot,"normal",0); + OV_BIND_METHOD(CursorSingleton::get_animationLength,"normal"); + OV_BIND_METHOD(CursorSingleton::get_displayRates,"normal"); + OV_BIND_METHOD(CursorSingleton::get_sequence,"normal"); + OV_BIND_METHOD(CursorSingleton::get_resolutions,"normal"); +} + +CursorSingleton* CursorSingleton::get_singleton() { + return _singleton; +} + +CursorSingleton::CursorSingleton() { + ERR_FAIL_COND(_singleton != nullptr); + _singleton = this; +} + +CursorSingleton::~CursorSingleton() { + ERR_FAIL_COND(_singleton != this); + _singleton = nullptr; +} + + +Ref CursorSingleton::get_image(String const& name, int index){ + if(index < cursors[name].images.size()){ + return cursors[name].images[index]; + } + return nullptr; +} + +Vector2i CursorSingleton::get_hotspot(String const& name, int index){ + if(index < cursors[name].hotspots.size()){ + return cursors[name].hotspots[index]; + } + return Vector2i(0,0); +} + +int CursorSingleton::get_animationLength(String const& name){ + return cursors[name].animationLength; +} + +TypedArray CursorSingleton::get_resolutions(String const& name){ + return cursors[name].resolutions; +} + +TypedArray CursorSingleton::get_displayRates(String const& name){ + if(cursors[name].displayRates.has_value()){ + return cursors[name].displayRates.value(); + } + return TypedArray(); +} + +TypedArray CursorSingleton::get_sequence(String const& name){ + if(cursors[name].displayRates.has_value()){ + return cursors[name].sequence.value(); + } + return TypedArray(); +} + +//, std::string_view const& base_folder +String CursorSingleton::to_define_file_name(String const& path) const { + String name = path.replace("\\","/"); + return name.get_slice("gfx/cursors/",1).get_slice(".",0); +} + +bool CursorSingleton::load_cursors() { + GameSingleton* game_singleton = GameSingleton::get_singleton(); + ERR_FAIL_NULL_V_MSG(game_singleton, false, vformat("Error retrieving GameSingleton")); + + static constexpr std::string_view cursor_directory = "gfx/cursors"; + bool ret = true; + + //there is also a png file in the folder we don't want to bother loading + //so don't just load everything in the directory + + //We need to load both ".cur" and ".CUR" files + Dataloader::path_vector_t cursor_files = game_singleton->get_dataloader() + .lookup_files_in_dir_recursive(cursor_directory, ".cur"); + + Dataloader::path_vector_t CURsor_files = game_singleton->get_dataloader() + .lookup_files_in_dir_recursive(cursor_directory, ".CUR"); + cursor_files.insert(std::end(cursor_files),std::begin(CURsor_files),std::end(CURsor_files)); + + Dataloader::path_vector_t animated_cursor_files = game_singleton->get_dataloader() + .lookup_files_in_dir_recursive(cursor_directory, ".ani"); + + if(cursor_files.size() < 1 && animated_cursor_files.size() < 1){ + Logger::error("failed to load cursors: no files in cursors directory"); + ret = false; + } + + for(std::filesystem::path const& file_name : cursor_files) { + String file = std_to_godot_string(file_name.string()); //.stem() + String name = to_define_file_name(file); + + if(!_load_cursor_cur(name,file)){ + Logger::error("failed to load normal cursor at path ",file_name); + ret = false; + } + } + for(std::filesystem::path const& file_name : animated_cursor_files) { + String file = std_to_godot_string(file_name.string()); //.stem() + String name = to_define_file_name(file); + + if(!_load_cursor_ani(name,file)){ + Logger::error("failed to load animated cursor at path ",file_name); + ret = false; + } + } + + return ret; +} + +bool CursorSingleton::_load_cursor_ani(String const& name, String const& path) { + const Ref file = FileAccess::open(path, FileAccess::ModeFlags::READ); + + //todo make the size min(file's size, file's declared size) just like sound + //read the RIFF container + String riff_id = read_riff_str(file); + int riff_size = std::min(static_cast(file->get_32()), file->get_length());; + String form_type = read_riff_str(file); + + //important variables + std::vector hotspots; + std::vector> images; + godot::TypedArray resolutions; + godot::TypedArray displayRates; + godot::TypedArray sequence; + + //ani header variables + int numFrames; + int numSteps; + Vector2i dimensions; + int bitCount; + int numPlanes; //??? + int displayRate; //how long each frame should last + int flags; + bool iconFlag; + bool sequenceFlag; + + + while(file->get_position() < riff_size){ + String id = read_riff_str(file); + int size = file->get_32(); + if(id == "LIST"){ + String list_type = read_riff_str(file); + } + else if(id == "anih"){ + //hack for some files, there's likely a better way + if(size == 36){ + int headerSize = file->get_32(); + } + numFrames = file->get_32(); + numSteps = file->get_32(); + dimensions = Vector2i(file->get_32(),file->get_32()); + bitCount = file->get_32(); + numPlanes = file->get_32(); + displayRate = file->get_32(); + flags = file->get_32(); + iconFlag = flags & 0x1; + sequenceFlag = flags & 0x2; + } + else if(id == "icon"){ + + int file_access_offset = file->get_position(); + + image_hotspot_pair_asset_t pair = _load_pair(file); + //basically pushback an array + + images.insert(std::end(images),std::begin(pair.images),std::end(pair.images)); + hotspots.insert(std::end(hotspots),std::begin(pair.hotspots),std::end(pair.hotspots)); + + //only store the resolutions from one frame + if(resolutions.is_empty()){ + for(int i=0;iget_width(),pair.images[i]->get_height())); + } + } + + //cursor could have been anywhere in the file, come back to a known position + file->seek(file_access_offset + size); + + } + else if(id == "seq "){ + for(int i=0;iget_32()); + } + } + else if(id == "rate"){ + for(int i=0;iget_32()/60.0); + } + } + else { + //Various junk (JUNK, metadata we don't care about, ...) + file->get_buffer(size); + } + //align to even bytes + if(file->get_position() % 2 != 0){ + file->get_8(); + } + } + + //not all ani files have the sequence and rate chunks, if not, fill out these properties + //manually + if(sequence.size() == 0){ + for(int i=0; i(sequence.size()) + }; + + cursor.displayRates = displayRates; + cursor.sequence = sequence; + cursors.emplace(std::move(name),cursor); + + return true; +} + +//set size if its an info string, otherwise leaving +String CursorSingleton::read_riff_str(Ref const& file, int size) const { + return file->get_buffer(size).get_string_from_ascii(); +} + +bool CursorSingleton::_load_cursor_cur(String const& name, String const& path) { + const Ref file = FileAccess::open(path, FileAccess::ModeFlags::READ); + image_hotspot_pair_asset_t pair = _load_pair(file); + + godot::TypedArray resolutions; + for(int i=0;iget_width(),pair.images[i]->get_height())); + } + + cursor_asset_t cursor = { + pair.hotspots, + pair.images, + resolutions, + 1 + }; + cursors.emplace(std::move(name),cursor); + return true; +} + +//used to load a .cur file from a file (could be the a whole .cur file, or a .cur within a .ani file) +CursorSingleton::image_hotspot_pair_asset_t CursorSingleton::_load_pair(godot::Ref const& file) { + image_hotspot_pair_asset_t pairs = {}; + + //.cur's within .anis won't start of the beginning of the file, so save where they start + int baseOffset = file->get_position(); + + //.cur header + int reserved = file->get_16(); + int type = file->get_16(); //1=ico, 2=cur + int imagesCount = file->get_16(); + + //all the images + for(int i=0; iget_8(); + int imgReserved = file->get_8(); + + Vector2i hotspot = Vector2i(); + hotspot.x = file->get_16(); + hotspot.y = file->get_16(); + + int dataSize = std::min(static_cast(file->get_32()), file->get_length() - file->get_position()); + int dataOffset = file->get_32(); + + //This image header information is sequential in the data, but the images aren't necessarily + // so save the current position, get the image data and return so we're ready for the next image header + int endOfImageHeader = file->get_position(); + + file->seek(dataOffset+baseOffset); + PackedByteArray const& imageData = file->get_buffer(dataSize); + file->seek(endOfImageHeader); + + Ref image = Ref(); + image.instantiate(); + + //PNGs are stored in their entirety, so use Godot's internal loader + if(imageData.slice(1,4).get_string_from_ascii() == "PNG") { + image->load_png_from_buffer(imageData); + } + else { //BMP based cursor, have to load this manually + int dibHeaderSize = imageData.decode_u32(0); + + //this is the combined sized of the picture and the transparency bitmask + // (ex. 32x32 dimension image becomes 32x64 here) + Vector2i combinedDimensions = Vector2i(imageData.decode_u32(4),imageData.decode_u32(8)); + int colourPlanes = imageData.decode_u16(12); + int bitsPerPixel = imageData.decode_u16(14); + if(bitsPerPixel <= 8 || bitsPerPixel == 24){ + Logger::warning("Attempting to import ", bitsPerPixel, "bit cursor, this may not be supported"); + } + else if(bitsPerPixel != 32){ + Logger::error("Invalid or Unsupported bits per pixel while loading cursor image, bpp: ", bitsPerPixel, "loading blank, transparent image instead"); + } + + int size = imageData.decode_u32(20); + Vector2i resolution = Vector2i(imageData.decode_s32(24),imageData.decode_s32(28)); + int paletteSize = imageData.decode_u32(32); + + if(paletteSize == 0 && bitsPerPixel <= 8){ + paletteSize = static_cast(pow(2, bitsPerPixel)); + } + int importantColours = imageData.decode_u32(36); + + //for BMPs with 8 bits per pixel or less, the pixel data is actually a lookup to this table here + PackedByteArray const& palette = imageData.slice(40,40+(4*paletteSize)); + + // this is where the image data starts + int offset = 40 + paletteSize*4; + + //where the transparency AND mask starts + int maskOffset = offset + _get_row_start(dimensions,dimensions.y,bitsPerPixel); //TODO + + PackedByteArray pixelData = PackedByteArray(); + + int i=0; + for(int row=0; row < dimensions.y; row++) { + for(int col=0; col < dimensions.x; col++) { + Vector2i coord = Vector2i(col,row); + bool transparent = _read_AND_mask( + imageData,coord,dimensions,maskOffset + ); + if(bitsPerPixel <= 8){ + //mostly legacy files, these ones all use a lookup into the colour palette + pixelData.append_array(_pixel_palette_lookup( + imageData,palette,coord,dimensions,offset,transparent,bitsPerPixel + )); + }/* + else if(bitsPerPixel == 16) { //TODO + //Unsupported, error + }*/ + else if(bitsPerPixel == 24) { + //Support Questionable, based on 1 example on the internet as opposed to the actual spec + pixelData.append_array(_read_24bit_pixel( + i,offset,imageData,transparent + )); + } + else if(bitsPerPixel == 32) { + //What vic actually uses + pixelData.append_array(_read_32bit_pixel( + i,offset,imageData,transparent + )); + } + else { + //Invalid bitsPerPixel, + //just give a blank pixel, we already emitted the appropriate error message + pixelData.append(0); + pixelData.append(0); + pixelData.append(0); + pixelData.append(0); + } + i++; + } + } + + image = image->create_from_data(dimensions.x,dimensions.y,false, Image::FORMAT_RGBA8,pixelData); + //bmp images are stored bottom to top + image->flip_y(); + } + Ref imageTexture = Ref(); + imageTexture.instantiate(); + + imageTexture = imageTexture->create_from_image(image); + + if(imageTexture.is_null()){ + Logger::error("Image Texture ",godot_to_std_string(file->get_path())," was null!"); + } + + pairs.hotspots.push_back(hotspot); + pairs.images.push_back(imageTexture); + + } + return pairs; + +} + +bool CursorSingleton::_read_AND_mask(PackedByteArray const& data, Vector2i pixelCoords, Vector2i dimensions, int offset){ + int rowStart = _get_row_start(dimensions, pixelCoords.y, 1); + int andBit = _select_bits(data, rowStart + offset,pixelCoords.x, 1); + return !andBit; +} + +PackedByteArray CursorSingleton::_pixel_palette_lookup(PackedByteArray const& data, PackedByteArray const& palette, Vector2i coord, Vector2i dimensions, int offset, bool transparent, int bitsPerPixel){ + + int rowStart = _get_row_start(dimensions, coord.y, bitsPerPixel); + int pixelBits = _select_bits(data, rowStart + offset, coord.x*bitsPerPixel, bitsPerPixel); + if((pixelBits+1)*4 > palette.size()){ + Logger::error("attempted to select invalid colour palette entry, ", pixelBits); + } + + //pixel bits serves as an index into the colour palette. We need to multiply the index by the number of bytes per colour (4) + PackedByteArray pixel = palette.slice(pixelBits*4,(pixelBits+1)*4); + pixel[3] = 0xFF * transparent; + return pixel; +} + +/* +24bit pixel support here is questionable: +the spec (per daubnet) says we should pad bytes to end things on 32bit boundaries +but the singular example of a 24bit cursor found on the internet does things like this. +So emit a warning when trying to load one of these +*/ +PackedByteArray CursorSingleton::_read_24bit_pixel(int i, int offset, PackedByteArray const& imageData, bool notTransparent) { + PackedByteArray pixel = PackedByteArray(); + + int b = imageData[offset + (i*3) + 0]; + int g = imageData[offset + (i*3) + 1]; + int r = imageData[offset + (i*3) + 2]; + //int x = imageData[offset + (i*4) + 3] * notTransparent; + + pixel.append(r); + pixel.append(g); + pixel.append(b); + pixel.append(0xFF*notTransparent); + + return pixel; +} + +PackedByteArray CursorSingleton::_read_32bit_pixel(int i, int offset, PackedByteArray const& imageData, bool notTransparent) { + PackedByteArray pixel = PackedByteArray(); + + int b = imageData[offset + (i*4) + 0]; + int g = imageData[offset + (i*4) + 1]; + int r = imageData[offset + (i*4) + 2]; + int a = imageData[offset + (i*4) + 3] * notTransparent; + + pixel.append(r); + pixel.append(g); + pixel.append(b); + pixel.append(a); + + return pixel; +} + +int CursorSingleton::_get_row_start(Vector2i dimensions, int y_coord, int bitsPerPixel){ + int rowCount = dimensions.x * bitsPerPixel / 32; + bool hasExtraRow = ((dimensions.x * bitsPerPixel) % 32) != 0; + rowCount += 1*hasExtraRow; + return rowCount*y_coord*4; //4 bytes per row * rows down +} + +int CursorSingleton::_select_bits(PackedByteArray data, int rowStart, int firstBit, int bitCount){ + int bitmasks[] = {0b1 , 0b11, 0b111, 0b1111, 0b11111, 0b111111, 0b1111111, 0b11111111}; + int byteIndex = firstBit / 8; + int bitInByteIndex = firstBit % 8; + if(bitInByteIndex + bitCount > 8){ + Logger::error("Attempted to select bits outside of a byte."); + return 0; + } + int byte = _reverse_bits(data[rowStart + byteIndex]); + int selected = (byte >> bitInByteIndex) & bitmasks[bitCount-1]; + + //TODO: questionable hack, nothing in the spec suggests we should need to do this + if(bitCount > 1 && selected != 0){ + return _rotate_right(selected,4); + } + return selected; +} + +int CursorSingleton::_reverse_bits(int byte, int bitsPerPixel){ + int reverser_lookup[] = { + 0x0, 0x8, 0x4, 0xc, 0x2, 0xa, 0x6, 0xe, + 0x1, 0x9, 0x5, 0xd, 0x3, 0xb, 0x7, 0xf + }; + int a = reverser_lookup[(byte & 0b1111)] << 4; + int b = reverser_lookup[byte >> 4]; + int c = b | a; + return c >> (8-bitsPerPixel); +} + +int CursorSingleton::_rotate_right(int byte, int size){ + return ((byte & 0b1) << (size-1)) | (byte >> 1); +} + +int CursorSingleton::_load_int_256(godot::Ref const& file){ + int value = file->get_8(); + if(value == 0) value = 256; + return value; +} \ No newline at end of file diff --git a/extension/src/openvic-extension/singletons/CursorSingleton.hpp b/extension/src/openvic-extension/singletons/CursorSingleton.hpp new file mode 100644 index 00000000..ec7a2273 --- /dev/null +++ b/extension/src/openvic-extension/singletons/CursorSingleton.hpp @@ -0,0 +1,97 @@ +#pragma once + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include + +namespace OpenVic { + + class CursorSingleton : public godot::Object { + + /* + This singleton only handles the dataloading for the cursors. + The functionality for actually setting one of these as a cursor + and animating animated cursors is done by the CursorManager autoloaded + gd script. With the exception of the shapeMap and its functions?? + */ + + GDCLASS(CursorSingleton, godot::Object); + static inline CursorSingleton* _singleton = nullptr; + + //an intermediate data type to help with loading cursors + //the size of images/hotspots corresponds to resolutionsPerCursor + struct image_hotspot_pair_asset_t { + std::vector hotspots; + std::vector> images; + }; + + //.cur files use all but the last 2 properties, rest are for .ani + //use vector2i instead of ref because of "unreference is not a member of godot::vector2i" + struct cursor_asset_t { + std::vector hotspots; + std::vector> images; + godot::TypedArray resolutions; + int animationLength; //1 for static cursors + std::optional> displayRates; + std::optional> sequence; + }; + + //map of "subfolder/fileName.cur/.ani" -> cursor asset, subfolder comes after gfx/cursor + using cursor_map_t = deque_ordered_map; + cursor_map_t cursors; + + + public: + CursorSingleton(); + ~CursorSingleton(); + static CursorSingleton* get_singleton(); + + protected: + static void _bind_methods(); + + godot::String to_define_file_name(godot::String const& path) const; + godot::String read_riff_str(godot::Ref const& file, int size=4) const; + + private: + //helper functions for the loaders + bool _read_AND_mask(godot::PackedByteArray const& data, godot::Vector2i pixelCoords, godot::Vector2i dimensions, int offset); + godot::PackedByteArray _pixel_palette_lookup(godot::PackedByteArray const& data, godot::PackedByteArray const& palette, godot::Vector2i coord, godot::Vector2i dimensions, int offset, bool transparent, int bitsPerPixel); + godot::PackedByteArray _read_32bit_pixel(int i, int offset, godot::PackedByteArray const& imageData, bool notTransparent); + godot::PackedByteArray _read_24bit_pixel(int i, int offset, godot::PackedByteArray const& imageData, bool notTransparent); + + int _get_row_start(godot::Vector2i dimensions, int y_coord, int bitsPerPixel); + int _select_bits(godot::PackedByteArray data, int rowStart, int firstBit, int bitCount); + int _reverse_bits(int byte, int bitsPerPixel=8); + int _rotate_right(int byte, int size=8); + int _load_int_256(godot::Ref const& file); + + //primary loading functions + image_hotspot_pair_asset_t _load_pair(godot::Ref const& file); + bool _load_cursor_ani(godot::String const& name, godot::String const& path); + bool _load_cursor_cur(godot::String const& name, godot::String const& path); + + public: + bool load_cursors(); + + //getters for the different cursor properties + godot::Ref get_image(godot::String const& name, int index = 0); + godot::Vector2i get_hotspot(godot::String const& name, int index = 0); + int get_animationLength(godot::String const& name); + godot::TypedArray get_resolutions(godot::String const& name); + godot::TypedArray get_displayRates(godot::String const& name); + godot::TypedArray get_sequence(godot::String const& name); + + }; + +} \ No newline at end of file diff --git a/extension/src/openvic-extension/singletons/SoundSingleton.cpp b/extension/src/openvic-extension/singletons/SoundSingleton.cpp index a32d9fef..67fa71ce 100644 --- a/extension/src/openvic-extension/singletons/SoundSingleton.cpp +++ b/extension/src/openvic-extension/singletons/SoundSingleton.cpp @@ -1,7 +1,7 @@ #include "SoundSingleton.hpp" #include -#include +//#include #include #include diff --git a/game/project.godot b/game/project.godot index 40e67126..6666eeed 100644 --- a/game/project.godot +++ b/game/project.godot @@ -39,6 +39,7 @@ MusicConductor="*res://src/Game/MusicConductor/MusicConductor.tscn" Keychain="*res://addons/keychain/Keychain.gd" GuiScale="*res://src/Game/Autoload/GuiScale.gd" SaveManager="*res://src/Game/Autoload/SaveManager.gd" +CursorManager="*res://src/Game/Autoload/CursorManager.gd" [display] diff --git a/game/src/Game/Autoload/CursorManager.gd b/game/src/Game/Autoload/CursorManager.gd new file mode 100644 index 00000000..540e149d --- /dev/null +++ b/game/src/Game/Autoload/CursorManager.gd @@ -0,0 +1,211 @@ +extends Node + +class compat_Cursor: + #cursor properties + var cursor_name:String + var resolutions:Array[Vector2i] + var numFrames:int = 1 + var is_animated:bool = false + var sequence:Array[int] = [1] + var timings:Array[float] = [1.0] + var shape:Input.CursorShape = Input.CURSOR_ARROW + + #TODO: Should this be kept, improved? this can only store 1 set of new cursors + #if the prefered resolution doesn't exist, generate new frames and hotspots + #and store them here + var custom_res_frames:Array[ImageTexture] = [] + var custom_res_hotspots:Array[Vector2i] = [] + + #Cursor state + var picked_resolution_index : int = 0 + var currentFrame : int = 0 + var timeToFrame : float = 1.0 + + func _init(nameIn:String, shape:Input.CursorShape = Input.CURSOR_ARROW) -> void: + self.cursor_name = nameIn + self.resolutions = CursorSingleton.get_resolutions(self.cursor_name) + self.numFrames = CursorSingleton.get_animationLength(self.cursor_name) + self.picked_resolution_index = 0 + self.shape = shape + + self.is_animated = self.numFrames > 1 + if self.is_animated: + self.sequence = CursorSingleton.get_sequence(self.cursor_name) + self.timings = CursorSingleton.get_displayRates(self.cursor_name) + self.timeToFrame = self.timings[self.sequence[currentFrame]] + + func set_resolution(resolution : Vector2i) -> void: + var index = resolutions.find(resolution) + if index != -1: + picked_resolution_index = index + return + if len(custom_res_frames) > 0 and Vector2i(custom_res_frames[0].get_size()) == resolution: + picked_resolution_index = -1 + return + + generate_new_res(picked_resolution_index,resolution) + picked_resolution_index = -1 + + func generate_new_res(base_res_index:int, resolution:Vector2i): + # resolution wasn't in among the default, need to generate it ourselves + for i in range(numFrames): + var tex = get_image(i,picked_resolution_index) + var image = Image.new() + image.copy_from(tex.get_image()) + image.resize(resolution.x,resolution.y,Image.INTERPOLATE_BILINEAR) + var orig_res:Vector2i = tex.get_size() + var hotspot = get_hotspot(i,picked_resolution_index) + + var y = ImageTexture.new() + custom_res_frames.push_back(y.create_from_image(image)) + custom_res_hotspots.push_back(hotspot * (resolution/orig_res)) + + #only bother with this if the cursor is animated + func _process_cursor(delta:float, advanceFrame:bool=true) -> void: + timeToFrame -= delta + if(timeToFrame <= 0): + if advanceFrame: + currentFrame = (currentFrame + 1) % self.numFrames + + # we dont use _calculate_index here because that's for images which + # correspond to both an animation frame and a resolution, whereas timings + # only correspond to an animation frame + timeToFrame = timings[self.sequence[currentFrame]] + set_hardware_cursor(currentFrame) + + func set_hardware_cursor(frame:int=0) -> void: + if picked_resolution_index == -1: + #if this is a custom resolution, skip the _calculate_index call + #made to find the correct index when there are multiple resolutions + #contained in a list, just get the frame in the custom array + if frame < len(custom_res_frames): + var texture = custom_res_frames[frame] + var hotspot = custom_res_hotspots[frame] + Input.set_custom_mouse_cursor(texture,shape,hotspot) + else: + push_warning("error fetching cursor image for custom resolution: frame number too large") + return + + var texture = get_image(frame,picked_resolution_index) + var hotspot = get_hotspot(frame,picked_resolution_index) + + Input.set_custom_mouse_cursor(texture,shape,hotspot) + + func get_image(frame:int=0, resolution_index:int=0) -> ImageTexture: + if frame < len(sequence): + return CursorSingleton.get_image(self.cursor_name,_calculate_index(frame,resolution_index)) + else: + push_warning("Error fetching cursor image: Frame number was larger than sequence length %s > %s" % [frame,len(self.sequence)]) + return null + + func get_hotspot(frame:int=0, resolution_index=0) -> Vector2i: + if frame < len(sequence): + return CursorSingleton.get_hotspot(self.cursor_name,_calculate_index(frame,resolution_index)) + else: + push_warning("Error fetching cursor hotspot: Frame number was larger than sequence length %s > %s" % [frame,len(self.sequence)]) + return Vector2i(0,0) + + func _calculate_index(frame:int=0, resolution_index=0) -> int: + if !is_animated: + return resolution_index + return resolution_index + len(resolutions)*sequence[frame] + +#Cursor singleton is capable of loading the data for images +#but managing animated cursors must be done in an autoload (part of the scene tree) + +var mouseOverWindow : bool = false +var windowFocused : bool = false +var activeCursor : compat_Cursor + +#TODO: This is set on game start, but we probably want this to be a video setting +var preferred_res : Vector2i = Vector2i(32,32) + +#Shape > Cursor dictionnaries +var currentCursors : Dictionary = { + Input.CURSOR_ARROW:null, + Input.CURSOR_BUSY:null, + Input.CURSOR_IBEAM:null +} +var queuedCursors : Dictionary = { + Input.CURSOR_ARROW:null, + Input.CURSOR_BUSY:null, + Input.CURSOR_IBEAM:null +} + +#temp +var cur_ind = 0 +var cooldown = 0.0 + +#Handle queued cursor changes and cursor animations +func _process(delta) -> void: + #test code + cooldown -= delta + var cursor_names = [ + "aero_busy", "drum", "aero_link_i", + "attack_move","busy","cant_move","deploy_not_valid","deploy_valid","dragselect", + "embark","exploration","friendly_move","no_move","normal","objective","selected" + ] + if Input.is_mouse_button_pressed(MOUSE_BUTTON_RIGHT) and cooldown <= 0: + cooldown = 0.3 + cur_ind = (cur_ind + 1) % cursor_names.size() + set_compat_cursor(cursor_names[cur_ind]) + #end test code + + #only attempt to update the mouse when this wont crash anything + if mouseOverWindow: + var advanceFrame = true #dont go to next frame if we just switched cursors + var mouseShape:Input.CursorShape = Input.get_current_cursor_shape() + + #instead of changeQueued, we need to check if the queued cursor pointer matches the current pointer + for shape in currentCursors.keys(): + if currentCursors[shape] != queuedCursors[shape] and queuedCursors[shape] != null: + currentCursors[shape] = queuedCursors[shape] + currentCursors[shape].set_hardware_cursor(0) + if mouseShape == shape: + advanceFrame = false + + if activeCursor != null and mouseShape != activeCursor.shape: + #Current mouse type changed, need to make sure that if the cursor of this new type + # is animated, we are providing its frames instead of the frames of the previous active cursor + activeCursor.currentFrame = 0 # reset the frame in the sequence to use + activeCursor.set_hardware_cursor(0) + + if mouseShape in currentCursors: + activeCursor = currentCursors[mouseShape] + activeCursor.currentFrame = 0 + advanceFrame = false + + #if we didnt change cursors, do an update + if activeCursor != null and activeCursor.is_animated: + activeCursor._process_cursor(delta,advanceFrame) + + +func set_prefered_res(res_in:Vector2i) -> void: + preferred_res = res_in + +#override_other_queued is to stop an animation frame from taking precedence over +#a cursor switch +func set_compat_cursor(cursor_name:String, cursor_shape:Input.CursorShape = Input.CURSOR_ARROW) -> void: + activeCursor = compat_Cursor.new(cursor_name,cursor_shape) + activeCursor.set_resolution(preferred_res) + set_mouse_cursor(activeCursor) + +# To safely change the mouse cursor, the mouse must be over the window +# these 2 functions help ensure we do it safely +func set_mouse_cursor(cursor:compat_Cursor) -> void: + if mouseOverWindow and windowFocused: + activeCursor = cursor + currentCursors[cursor.shape] = cursor + else: + queuedCursors[cursor.shape] = cursor + +func _notification(what): + match(what): + NOTIFICATION_WM_MOUSE_ENTER: + mouseOverWindow = true + NOTIFICATION_WM_MOUSE_EXIT: + mouseOverWindow = false + NOTIFICATION_WM_WINDOW_FOCUS_IN: + windowFocused = true + NOTIFICATION_WM_WINDOW_FOCUS_OUT: + windowFocused = false diff --git a/game/src/Game/GameStart.gd b/game/src/Game/GameStart.gd index 3c046a01..bdd020c1 100644 --- a/game/src/Game/GameStart.gd +++ b/game/src/Game/GameStart.gd @@ -107,9 +107,14 @@ func _setup_compatibility_mode_paths() -> void: func _load_compatibility_mode() -> void: if GameSingleton.set_compatibility_mode_roots(_compatibility_path_list) != OK: push_error("Errors setting game roots!") - + + CursorSingleton.load_cursors() + CursorManager.set_prefered_res(Vector2i(48,48)) + CursorManager.set_compat_cursor("normal",Input.CURSOR_ARROW) + CursorManager.set_compat_cursor("busy",Input.CURSOR_IBEAM) + setup_title_theme() - + if GameSingleton.load_defines_compatibility_mode() != OK: push_error("Errors loading game defines!") diff --git a/game/src/Game/MusicConductor/MusicConductor.gd b/game/src/Game/MusicConductor/MusicConductor.gd index 7103aff1..7aa8a6ef 100644 --- a/game/src/Game/MusicConductor/MusicConductor.gd +++ b/game/src/Game/MusicConductor/MusicConductor.gd @@ -95,7 +95,10 @@ func select_previous_song() -> void: func setup_compat_song(file_name) -> void: var song = SongInfo.new() var stream = SoundSingleton.get_song(file_name) - + if stream == null: + push_error("Audio Stream for compat song %s was null" % file_name) + return + var metadata = MusicMetadata.new() metadata.set_from_stream(stream) var title = metadata.title From 267e57094d661766f726da4031bdef433d254138 Mon Sep 17 00:00:00 2001 From: Nemrav <> Date: Mon, 19 Aug 2024 23:19:23 -0300 Subject: [PATCH 2/9] refactor frame storage and res generation --- .../singletons/CursorSingleton.cpp | 110 ++++++++++++---- .../singletons/CursorSingleton.hpp | 10 +- game/src/Game/Autoload/CursorManager.gd | 120 +++++++----------- game/src/Game/GameStart.gd | 1 + 4 files changed, 134 insertions(+), 107 deletions(-) diff --git a/extension/src/openvic-extension/singletons/CursorSingleton.cpp b/extension/src/openvic-extension/singletons/CursorSingleton.cpp index eb20194f..22975e57 100644 --- a/extension/src/openvic-extension/singletons/CursorSingleton.cpp +++ b/extension/src/openvic-extension/singletons/CursorSingleton.cpp @@ -20,12 +20,13 @@ using namespace OpenVic::NodeTools; void CursorSingleton::_bind_methods() { OV_BIND_METHOD(CursorSingleton::load_cursors); - OV_BIND_METHOD(CursorSingleton::get_image,"normal",0); - OV_BIND_METHOD(CursorSingleton::get_hotspot,"normal",0); + OV_BIND_METHOD(CursorSingleton::get_frames,"normal",0); + OV_BIND_METHOD(CursorSingleton::get_hotspots,"normal",0); OV_BIND_METHOD(CursorSingleton::get_animationLength,"normal"); OV_BIND_METHOD(CursorSingleton::get_displayRates,"normal"); OV_BIND_METHOD(CursorSingleton::get_sequence,"normal"); OV_BIND_METHOD(CursorSingleton::get_resolutions,"normal"); + OV_BIND_METHOD(CursorSingleton::generate_resolution,"normal",0,Vector2i(64,64)); } CursorSingleton* CursorSingleton::get_singleton() { @@ -43,18 +44,18 @@ CursorSingleton::~CursorSingleton() { } -Ref CursorSingleton::get_image(String const& name, int index){ - if(index < cursors[name].images.size()){ - return cursors[name].images[index]; +Array CursorSingleton::get_frames(String const& name, int res_index){ + if(res_index < cursors[name].images.size()){ + return cursors[name].images[res_index]; } - return nullptr; + return Array(); } -Vector2i CursorSingleton::get_hotspot(String const& name, int index){ - if(index < cursors[name].hotspots.size()){ - return cursors[name].hotspots[index]; +TypedArray CursorSingleton::get_hotspots(String const& name, int res_index){ + if(res_index < cursors[name].images.size()){ + return cursors[name].hotspots[res_index]; } - return Vector2i(0,0); + return TypedArray(); } int CursorSingleton::get_animationLength(String const& name){ @@ -79,6 +80,39 @@ TypedArray CursorSingleton::get_sequence(String const& name){ return TypedArray(); } +void CursorSingleton::generate_resolution(String const& name, int base_res_index, Vector2i target_res){ + cursor_asset_t* cursor = &cursors[name]; + Array new_frameset; + TypedArray new_hotspots; + + for(int i=0;iimages[base_res_index].size();i++){ + + Ref texture = static_cast>(cursor->images[base_res_index][i]); + + Ref image; + image.instantiate(); + image->create(target_res.x, target_res.y, false, Image::Format::FORMAT_RGBA8); + image->copy_from(texture->get_image()); + image->resize(target_res.x,target_res.y,godot::Image::INTERPOLATE_BILINEAR); + + + Ref new_texture = Ref(); + new_texture.instantiate(); + new_texture->set_image(image); + + new_frameset.push_back(new_texture); + + Vector2i base_hotspot = cursor->hotspots[base_res_index][i]; + Vector2i scale_ratio = target_res / static_cast(cursor->resolutions[base_res_index]); + new_hotspots.push_back(base_hotspot*scale_ratio); + + } + + cursor->images.push_back(new_frameset); + cursor->hotspots.push_back(new_hotspots); + cursor->resolutions.push_back(target_res); +} + //, std::string_view const& base_folder String CursorSingleton::to_define_file_name(String const& path) const { String name = path.replace("\\","/"); @@ -143,11 +177,12 @@ bool CursorSingleton::_load_cursor_ani(String const& name, String const& path) { String form_type = read_riff_str(file); //important variables - std::vector hotspots; - std::vector> images; - godot::TypedArray resolutions; - godot::TypedArray displayRates; - godot::TypedArray sequence; + std::vector frames_by_resolution; + std::vector> hotspots_by_resolution; + + TypedArray resolutions; + TypedArray displayRates; + TypedArray sequence; //ani header variables int numFrames; @@ -188,15 +223,27 @@ bool CursorSingleton::_load_cursor_ani(String const& name, String const& path) { image_hotspot_pair_asset_t pair = _load_pair(file); //basically pushback an array - - images.insert(std::end(images),std::begin(pair.images),std::end(pair.images)); - hotspots.insert(std::end(hotspots),std::begin(pair.hotspots),std::end(pair.hotspots)); //only store the resolutions from one frame if(resolutions.is_empty()){ for(int i=0;i hotspots; + Array images; + images.push_back(pair.images[i]); + hotspots.push_back(pair.hotspots[i]); + frames_by_resolution.push_back(images); + hotspots_by_resolution.push_back(hotspots); + resolutions.push_back(Vector2i(pair.images[i]->get_width(),pair.images[i]->get_height())); } + + } + else { + for(int i=0;i(sequence.size()) }; @@ -259,14 +306,26 @@ bool CursorSingleton::_load_cursor_cur(String const& name, String const& path) { const Ref file = FileAccess::open(path, FileAccess::ModeFlags::READ); image_hotspot_pair_asset_t pair = _load_pair(file); - godot::TypedArray resolutions; + std::vector frames_by_resolution; + std::vector> hotspots_by_resolution; + + TypedArray resolutions; + for(int i=0;iget_width(),pair.images[i]->get_height())); + + Array frames; + frames.push_back(pair.images[i]); + frames_by_resolution.push_back(frames); + + TypedArray hotspots; + hotspots.push_back(pair.hotspots[i]); + hotspots_by_resolution.push_back(hotspots); } cursor_asset_t cursor = { - pair.hotspots, - pair.images, + hotspots_by_resolution, + frames_by_resolution, resolutions, 1 }; @@ -275,7 +334,7 @@ bool CursorSingleton::_load_cursor_cur(String const& name, String const& path) { } //used to load a .cur file from a file (could be the a whole .cur file, or a .cur within a .ani file) -CursorSingleton::image_hotspot_pair_asset_t CursorSingleton::_load_pair(godot::Ref const& file) { +CursorSingleton::image_hotspot_pair_asset_t CursorSingleton::_load_pair(Ref const& file) { image_hotspot_pair_asset_t pairs = {}; //.cur's within .anis won't start of the beginning of the file, so save where they start @@ -442,7 +501,6 @@ PackedByteArray CursorSingleton::_read_24bit_pixel(int i, int offset, PackedByte int b = imageData[offset + (i*3) + 0]; int g = imageData[offset + (i*3) + 1]; int r = imageData[offset + (i*3) + 2]; - //int x = imageData[offset + (i*4) + 3] * notTransparent; pixel.append(r); pixel.append(g); @@ -508,7 +566,7 @@ int CursorSingleton::_rotate_right(int byte, int size){ return ((byte & 0b1) << (size-1)) | (byte >> 1); } -int CursorSingleton::_load_int_256(godot::Ref const& file){ +int CursorSingleton::_load_int_256(Ref const& file){ int value = file->get_8(); if(value == 0) value = 256; return value; diff --git a/extension/src/openvic-extension/singletons/CursorSingleton.hpp b/extension/src/openvic-extension/singletons/CursorSingleton.hpp index ec7a2273..6f411416 100644 --- a/extension/src/openvic-extension/singletons/CursorSingleton.hpp +++ b/extension/src/openvic-extension/singletons/CursorSingleton.hpp @@ -38,9 +38,10 @@ namespace OpenVic { //.cur files use all but the last 2 properties, rest are for .ani //use vector2i instead of ref because of "unreference is not a member of godot::vector2i" + //images is an array of ImageTexture, just godot doesn't like typed arrays with image texture for some reason struct cursor_asset_t { - std::vector hotspots; - std::vector> images; + std::vector> hotspots; + std::vector images; godot::TypedArray resolutions; int animationLength; //1 for static cursors std::optional> displayRates; @@ -85,12 +86,13 @@ namespace OpenVic { bool load_cursors(); //getters for the different cursor properties - godot::Ref get_image(godot::String const& name, int index = 0); - godot::Vector2i get_hotspot(godot::String const& name, int index = 0); + godot::Array get_frames(godot::String const& name, int res_index = 0); + godot::TypedArray get_hotspots(godot::String const& name, int res_index = 0); int get_animationLength(godot::String const& name); godot::TypedArray get_resolutions(godot::String const& name); godot::TypedArray get_displayRates(godot::String const& name); godot::TypedArray get_sequence(godot::String const& name); + void generate_resolution(godot::String const& name, int base_res_index, godot::Vector2i target_res); }; diff --git a/game/src/Game/Autoload/CursorManager.gd b/game/src/Game/Autoload/CursorManager.gd index 540e149d..fd9a9dbe 100644 --- a/game/src/Game/Autoload/CursorManager.gd +++ b/game/src/Game/Autoload/CursorManager.gd @@ -3,32 +3,28 @@ extends Node class compat_Cursor: #cursor properties var cursor_name:String + var shape:Input.CursorShape = Input.CURSOR_ARROW + var resolutions:Array[Vector2i] - var numFrames:int = 1 + var frames:Array + var hotspots:Array[Vector2i] var is_animated:bool = false - var sequence:Array[int] = [1] + var sequence:Array[int] = [0] var timings:Array[float] = [1.0] - var shape:Input.CursorShape = Input.CURSOR_ARROW - - #TODO: Should this be kept, improved? this can only store 1 set of new cursors - #if the prefered resolution doesn't exist, generate new frames and hotspots - #and store them here - var custom_res_frames:Array[ImageTexture] = [] - var custom_res_hotspots:Array[Vector2i] = [] #Cursor state - var picked_resolution_index : int = 0 var currentFrame : int = 0 var timeToFrame : float = 1.0 - func _init(nameIn:String, shape:Input.CursorShape = Input.CURSOR_ARROW) -> void: + func _init(nameIn:String, shapeIn:Input.CursorShape = Input.CURSOR_ARROW) -> void: self.cursor_name = nameIn self.resolutions = CursorSingleton.get_resolutions(self.cursor_name) - self.numFrames = CursorSingleton.get_animationLength(self.cursor_name) - self.picked_resolution_index = 0 - self.shape = shape - self.is_animated = self.numFrames > 1 + self.shape = shapeIn + self.frames = CursorSingleton.get_frames(self.cursor_name,0) + self.hotspots = CursorSingleton.get_hotspots(self.cursor_name,0) + + self.is_animated = len(frames) > 1 if self.is_animated: self.sequence = CursorSingleton.get_sequence(self.cursor_name) self.timings = CursorSingleton.get_displayRates(self.cursor_name) @@ -37,78 +33,43 @@ class compat_Cursor: func set_resolution(resolution : Vector2i) -> void: var index = resolutions.find(resolution) if index != -1: - picked_resolution_index = index - return - if len(custom_res_frames) > 0 and Vector2i(custom_res_frames[0].get_size()) == resolution: - picked_resolution_index = -1 + frames = CursorSingleton.get_frames(cursor_name,index) return + + #couldnt find it, so generate it based on the highest res available + var highest_res_ind:int = 0 + var highest_res_x = 0 + for i in range(len(resolutions)): + if resolutions[i].x > highest_res_x: + highest_res_x = resolutions[i].x + highest_res_ind = i + generate_new_res(highest_res_ind,resolution) - generate_new_res(picked_resolution_index,resolution) - picked_resolution_index = -1 + self.resolutions = CursorSingleton.get_resolutions(self.cursor_name) + self.frames = CursorSingleton.get_frames(self.cursor_name,len(self.resolutions)-1) + self.hotspots = CursorSingleton.get_hotspots(self.cursor_name,len(self.resolutions)-1) - func generate_new_res(base_res_index:int, resolution:Vector2i): + assert(len(self.frames ) != 0) + + + func generate_new_res(base_res_index:int, resolution:Vector2i) -> void: # resolution wasn't in among the default, need to generate it ourselves - for i in range(numFrames): - var tex = get_image(i,picked_resolution_index) - var image = Image.new() - image.copy_from(tex.get_image()) - image.resize(resolution.x,resolution.y,Image.INTERPOLATE_BILINEAR) - var orig_res:Vector2i = tex.get_size() - var hotspot = get_hotspot(i,picked_resolution_index) - - var y = ImageTexture.new() - custom_res_frames.push_back(y.create_from_image(image)) - custom_res_hotspots.push_back(hotspot * (resolution/orig_res)) + CursorSingleton.generate_resolution(cursor_name,base_res_index,resolution) #only bother with this if the cursor is animated func _process_cursor(delta:float, advanceFrame:bool=true) -> void: timeToFrame -= delta if(timeToFrame <= 0): if advanceFrame: - currentFrame = (currentFrame + 1) % self.numFrames - - # we dont use _calculate_index here because that's for images which - # correspond to both an animation frame and a resolution, whereas timings - # only correspond to an animation frame + currentFrame = (currentFrame + 1) % len(sequence) timeToFrame = timings[self.sequence[currentFrame]] set_hardware_cursor(currentFrame) func set_hardware_cursor(frame:int=0) -> void: - if picked_resolution_index == -1: - #if this is a custom resolution, skip the _calculate_index call - #made to find the correct index when there are multiple resolutions - #contained in a list, just get the frame in the custom array - if frame < len(custom_res_frames): - var texture = custom_res_frames[frame] - var hotspot = custom_res_hotspots[frame] - Input.set_custom_mouse_cursor(texture,shape,hotspot) - else: - push_warning("error fetching cursor image for custom resolution: frame number too large") - return - - var texture = get_image(frame,picked_resolution_index) - var hotspot = get_hotspot(frame,picked_resolution_index) - + var texture = frames[sequence[frame]] + var hotspot = hotspots[sequence[frame]] Input.set_custom_mouse_cursor(texture,shape,hotspot) - func get_image(frame:int=0, resolution_index:int=0) -> ImageTexture: - if frame < len(sequence): - return CursorSingleton.get_image(self.cursor_name,_calculate_index(frame,resolution_index)) - else: - push_warning("Error fetching cursor image: Frame number was larger than sequence length %s > %s" % [frame,len(self.sequence)]) - return null - - func get_hotspot(frame:int=0, resolution_index=0) -> Vector2i: - if frame < len(sequence): - return CursorSingleton.get_hotspot(self.cursor_name,_calculate_index(frame,resolution_index)) - else: - push_warning("Error fetching cursor hotspot: Frame number was larger than sequence length %s > %s" % [frame,len(self.sequence)]) - return Vector2i(0,0) - - func _calculate_index(frame:int=0, resolution_index=0) -> int: - if !is_animated: - return resolution_index - return resolution_index + len(resolutions)*sequence[frame] #Cursor singleton is capable of loading the data for images #but managing animated cursors must be done in an autoload (part of the scene tree) @@ -121,6 +82,9 @@ var activeCursor : compat_Cursor var preferred_res : Vector2i = Vector2i(32,32) #Shape > Cursor dictionnaries +#NOTE: In terms of V2, this is unnecessary, as the only cursor that isn't of shape +#"arrow" in v2 is "busy", which needs to be manually triggered anyways like arrow. +#This is needed for shapes like IBEAM which get switched to automatically var currentCursors : Dictionary = { Input.CURSOR_ARROW:null, Input.CURSOR_BUSY:null, @@ -132,13 +96,13 @@ var queuedCursors : Dictionary = { Input.CURSOR_IBEAM:null } -#temp +#temp TODO: Remove when done testing var cur_ind = 0 var cooldown = 0.0 #Handle queued cursor changes and cursor animations func _process(delta) -> void: - #test code + #TODO: Remove test code when done testing cooldown -= delta var cursor_names = [ "aero_busy", "drum", "aero_link_i", @@ -163,6 +127,7 @@ func _process(delta) -> void: currentCursors[shape].set_hardware_cursor(0) if mouseShape == shape: advanceFrame = false + activeCursor = currentCursors[shape] if activeCursor != null and mouseShape != activeCursor.shape: #Current mouse type changed, need to make sure that if the cursor of this new type @@ -170,7 +135,7 @@ func _process(delta) -> void: activeCursor.currentFrame = 0 # reset the frame in the sequence to use activeCursor.set_hardware_cursor(0) - if mouseShape in currentCursors: + if mouseShape in currentCursors and currentCursors[mouseShape] != null: activeCursor = currentCursors[mouseShape] activeCursor.currentFrame = 0 advanceFrame = false @@ -186,9 +151,9 @@ func set_prefered_res(res_in:Vector2i) -> void: #override_other_queued is to stop an animation frame from taking precedence over #a cursor switch func set_compat_cursor(cursor_name:String, cursor_shape:Input.CursorShape = Input.CURSOR_ARROW) -> void: - activeCursor = compat_Cursor.new(cursor_name,cursor_shape) - activeCursor.set_resolution(preferred_res) - set_mouse_cursor(activeCursor) + var cursor = compat_Cursor.new(cursor_name,cursor_shape) + cursor.set_resolution(preferred_res) + set_mouse_cursor(cursor) # To safely change the mouse cursor, the mouse must be over the window # these 2 functions help ensure we do it safely @@ -196,6 +161,7 @@ func set_mouse_cursor(cursor:compat_Cursor) -> void: if mouseOverWindow and windowFocused: activeCursor = cursor currentCursors[cursor.shape] = cursor + queuedCursors[cursor.shape] = null else: queuedCursors[cursor.shape] = cursor diff --git a/game/src/Game/GameStart.gd b/game/src/Game/GameStart.gd index bdd020c1..bb6e2411 100644 --- a/game/src/Game/GameStart.gd +++ b/game/src/Game/GameStart.gd @@ -111,6 +111,7 @@ func _load_compatibility_mode() -> void: CursorSingleton.load_cursors() CursorManager.set_prefered_res(Vector2i(48,48)) CursorManager.set_compat_cursor("normal",Input.CURSOR_ARROW) + #TODO: Change Busy to be shape Input.CURSOR_BUSY when done testing CursorManager.set_compat_cursor("busy",Input.CURSOR_IBEAM) setup_title_theme() From fe8f794a061ebb8b55b566de34dbc844e1e9de1a Mon Sep 17 00:00:00 2001 From: Nemrav <> Date: Tue, 20 Aug 2024 12:56:18 -0300 Subject: [PATCH 3/9] cursorSingleton small revisions --- .../singletons/CursorSingleton.cpp | 28 +++++++++---------- .../singletons/CursorSingleton.hpp | 9 +++--- 2 files changed, 18 insertions(+), 19 deletions(-) diff --git a/extension/src/openvic-extension/singletons/CursorSingleton.cpp b/extension/src/openvic-extension/singletons/CursorSingleton.cpp index 22975e57..f8aebe56 100644 --- a/extension/src/openvic-extension/singletons/CursorSingleton.cpp +++ b/extension/src/openvic-extension/singletons/CursorSingleton.cpp @@ -2,14 +2,14 @@ #include -#include -#include #include #include #include +#include +#include -#include #include +#include using OpenVic::Utilities::godot_to_std_string; using OpenVic::Utilities::std_to_godot_string; @@ -93,7 +93,7 @@ void CursorSingleton::generate_resolution(String const& name, int base_res_index image.instantiate(); image->create(target_res.x, target_res.y, false, Image::Format::FORMAT_RGBA8); image->copy_from(texture->get_image()); - image->resize(target_res.x,target_res.y,godot::Image::INTERPOLATE_BILINEAR); + image->resize(target_res.x,target_res.y,Image::INTERPOLATE_BILINEAR); Ref new_texture = Ref(); @@ -116,11 +116,11 @@ void CursorSingleton::generate_resolution(String const& name, int base_res_index //, std::string_view const& base_folder String CursorSingleton::to_define_file_name(String const& path) const { String name = path.replace("\\","/"); - return name.get_slice("gfx/cursors/",1).get_slice(".",0); + return name.get_slice("gfx/cursors/",1).get_slice(".",0); } bool CursorSingleton::load_cursors() { - GameSingleton* game_singleton = GameSingleton::get_singleton(); + GameSingleton const* game_singleton = GameSingleton::get_singleton(); ERR_FAIL_NULL_V_MSG(game_singleton, false, vformat("Error retrieving GameSingleton")); static constexpr std::string_view cursor_directory = "gfx/cursors"; @@ -146,7 +146,7 @@ bool CursorSingleton::load_cursors() { } for(std::filesystem::path const& file_name : cursor_files) { - String file = std_to_godot_string(file_name.string()); //.stem() + String file = std_to_godot_string(file_name.string()); String name = to_define_file_name(file); if(!_load_cursor_cur(name,file)){ @@ -155,7 +155,7 @@ bool CursorSingleton::load_cursors() { } } for(std::filesystem::path const& file_name : animated_cursor_files) { - String file = std_to_godot_string(file_name.string()); //.stem() + String file = std_to_godot_string(file_name.string()); String name = to_define_file_name(file); if(!_load_cursor_ani(name,file)){ @@ -283,7 +283,7 @@ bool CursorSingleton::_load_cursor_ani(String const& name, String const& path) { } } - cursor_asset_t cursor = { + cursor_asset_t cursor = { hotspots_by_resolution, frames_by_resolution, resolutions, @@ -303,7 +303,7 @@ String CursorSingleton::read_riff_str(Ref const& file, int size) con } bool CursorSingleton::_load_cursor_cur(String const& name, String const& path) { - const Ref file = FileAccess::open(path, FileAccess::ModeFlags::READ); + const Ref file = FileAccess::open(path, FileAccess::ModeFlags::READ); image_hotspot_pair_asset_t pair = _load_pair(file); std::vector frames_by_resolution; @@ -323,13 +323,13 @@ bool CursorSingleton::_load_cursor_cur(String const& name, String const& path) { hotspots_by_resolution.push_back(hotspots); } - cursor_asset_t cursor = { + cursor_asset_t cursor = { hotspots_by_resolution, frames_by_resolution, resolutions, 1 }; - cursors.emplace(std::move(name),cursor); + cursors.emplace(std::move(name),cursor); return true; } @@ -404,7 +404,7 @@ CursorSingleton::image_hotspot_pair_asset_t CursorSingleton::_load_pair(Ref imageTexture = Ref(); imageTexture.instantiate(); - imageTexture = imageTexture->create_from_image(image); + imageTexture = imageTexture->create_from_image(image); if(imageTexture.is_null()){ Logger::error("Image Texture ",godot_to_std_string(file->get_path())," was null!"); diff --git a/extension/src/openvic-extension/singletons/CursorSingleton.hpp b/extension/src/openvic-extension/singletons/CursorSingleton.hpp index 6f411416..4ca2cad2 100644 --- a/extension/src/openvic-extension/singletons/CursorSingleton.hpp +++ b/extension/src/openvic-extension/singletons/CursorSingleton.hpp @@ -2,18 +2,17 @@ #include #include -#include -#include - #include #include #include #include +#include +#include +#include #include #include #include -#include namespace OpenVic { @@ -62,7 +61,7 @@ namespace OpenVic { static void _bind_methods(); godot::String to_define_file_name(godot::String const& path) const; - godot::String read_riff_str(godot::Ref const& file, int size=4) const; + godot::String read_riff_str(godot::Ref const& file, int size=4) const; private: //helper functions for the loaders From 5e95d0438041fb3a09332a81983139f47afbc7d6 Mon Sep 17 00:00:00 2001 From: Nemrav <> Date: Tue, 20 Aug 2024 15:15:33 -0300 Subject: [PATCH 4/9] cursors ready to test --- .../singletons/CursorSingleton.cpp | 15 +++++- .../singletons/CursorSingleton.hpp | 1 + game/src/Game/Autoload/CursorManager.gd | 50 +++++++++++++++---- game/src/Game/GameStart.gd | 3 +- 4 files changed, 56 insertions(+), 13 deletions(-) diff --git a/extension/src/openvic-extension/singletons/CursorSingleton.cpp b/extension/src/openvic-extension/singletons/CursorSingleton.cpp index f8aebe56..950990f2 100644 --- a/extension/src/openvic-extension/singletons/CursorSingleton.cpp +++ b/extension/src/openvic-extension/singletons/CursorSingleton.cpp @@ -20,13 +20,20 @@ using namespace OpenVic::NodeTools; void CursorSingleton::_bind_methods() { OV_BIND_METHOD(CursorSingleton::load_cursors); - OV_BIND_METHOD(CursorSingleton::get_frames,"normal",0); + OV_BIND_METHOD(CursorSingleton::get_frames,{"cursor name","resolution index"},"normal",0); OV_BIND_METHOD(CursorSingleton::get_hotspots,"normal",0); OV_BIND_METHOD(CursorSingleton::get_animationLength,"normal"); OV_BIND_METHOD(CursorSingleton::get_displayRates,"normal"); OV_BIND_METHOD(CursorSingleton::get_sequence,"normal"); OV_BIND_METHOD(CursorSingleton::get_resolutions,"normal"); OV_BIND_METHOD(CursorSingleton::generate_resolution,"normal",0,Vector2i(64,64)); + OV_BIND_METHOD(CursorSingleton::get_cursor_names); + + ADD_PROPERTY(PropertyInfo( + Variant::ARRAY, + "cursor_names", PROPERTY_HINT_ARRAY_TYPE, + "String"), + "", "get_cursor_names"); } CursorSingleton* CursorSingleton::get_singleton() { @@ -153,6 +160,9 @@ bool CursorSingleton::load_cursors() { Logger::error("failed to load normal cursor at path ",file_name); ret = false; } + else{ + cursor_names.append(name); + } } for(std::filesystem::path const& file_name : animated_cursor_files) { String file = std_to_godot_string(file_name.string()); @@ -162,6 +172,9 @@ bool CursorSingleton::load_cursors() { Logger::error("failed to load animated cursor at path ",file_name); ret = false; } + else{ + cursor_names.append(name); + } } return ret; diff --git a/extension/src/openvic-extension/singletons/CursorSingleton.hpp b/extension/src/openvic-extension/singletons/CursorSingleton.hpp index 4ca2cad2..f6911ba1 100644 --- a/extension/src/openvic-extension/singletons/CursorSingleton.hpp +++ b/extension/src/openvic-extension/singletons/CursorSingleton.hpp @@ -51,6 +51,7 @@ namespace OpenVic { using cursor_map_t = deque_ordered_map; cursor_map_t cursors; + godot::Array PROPERTY(cursor_names); public: CursorSingleton(); diff --git a/game/src/Game/Autoload/CursorManager.gd b/game/src/Game/Autoload/CursorManager.gd index fd9a9dbe..729aed5a 100644 --- a/game/src/Game/Autoload/CursorManager.gd +++ b/game/src/Game/Autoload/CursorManager.gd @@ -29,7 +29,11 @@ class compat_Cursor: self.sequence = CursorSingleton.get_sequence(self.cursor_name) self.timings = CursorSingleton.get_displayRates(self.cursor_name) self.timeToFrame = self.timings[self.sequence[currentFrame]] - + + func reset(): + currentFrame = 0 + timeToFrame = self.timings[self.sequence[0]] + func set_resolution(resolution : Vector2i) -> void: var index = resolutions.find(resolution) if index != -1: @@ -96,6 +100,18 @@ var queuedCursors : Dictionary = { Input.CURSOR_IBEAM:null } +var loaded_cursors : Dictionary = {} + +func load_cursors() -> void: + CursorSingleton.load_cursors() + for cursor_name in CursorSingleton.cursor_names: + var shape:Input.CursorShape = Input.CURSOR_ARROW + if cursor_name == "busy": + shape = Input.CURSOR_BUSY + var cursor = compat_Cursor.new(cursor_name,shape) + cursor.set_resolution(preferred_res) + loaded_cursors[cursor_name] = cursor + #temp TODO: Remove when done testing var cur_ind = 0 var cooldown = 0.0 @@ -120,27 +136,31 @@ func _process(delta) -> void: var advanceFrame = true #dont go to next frame if we just switched cursors var mouseShape:Input.CursorShape = Input.get_current_cursor_shape() - #instead of changeQueued, we need to check if the queued cursor pointer matches the current pointer for shape in currentCursors.keys(): if currentCursors[shape] != queuedCursors[shape] and queuedCursors[shape] != null: currentCursors[shape] = queuedCursors[shape] - currentCursors[shape].set_hardware_cursor(0) + currentCursors[shape].set_hardware_cursor() + #this is the currently active shape, set the active cursor, make it frame 0 and + #make sure we dont skip this frame if mouseShape == shape: advanceFrame = false activeCursor = currentCursors[shape] + activeCursor.reset() + #The mouse's cursor shape changed (something like we started hovering over text) + # reset the current cursor's frame, then switch the active cursor if activeCursor != null and mouseShape != activeCursor.shape: #Current mouse type changed, need to make sure that if the cursor of this new type # is animated, we are providing its frames instead of the frames of the previous active cursor - activeCursor.currentFrame = 0 # reset the frame in the sequence to use - activeCursor.set_hardware_cursor(0) + activeCursor.reset() # reset the frame in the sequence to use + activeCursor.set_hardware_cursor() if mouseShape in currentCursors and currentCursors[mouseShape] != null: activeCursor = currentCursors[mouseShape] - activeCursor.currentFrame = 0 + activeCursor.reset() advanceFrame = false - #if we didnt change cursors, do an update + #if we didnt change cursors and are animated, do an update if activeCursor != null and activeCursor.is_animated: activeCursor._process_cursor(delta,advanceFrame) @@ -150,18 +170,26 @@ func set_prefered_res(res_in:Vector2i) -> void: #override_other_queued is to stop an animation frame from taking precedence over #a cursor switch -func set_compat_cursor(cursor_name:String, cursor_shape:Input.CursorShape = Input.CURSOR_ARROW) -> void: - var cursor = compat_Cursor.new(cursor_name,cursor_shape) - cursor.set_resolution(preferred_res) - set_mouse_cursor(cursor) +func set_compat_cursor(cursor_name:String, cursor_shape:Input.CursorShape = -1) -> void: + #var cursor = compat_Cursor.new(cursor_name,cursor_shape) + if cursor_name in loaded_cursors: + var cursor = loaded_cursors[cursor_name] + if cursor_shape != -1: + cursor.shape = cursor_shape + cursor.set_resolution(preferred_res) + set_mouse_cursor(cursor) + else: + push_warning("Cursor name %s is not among loaded cursors" % cursor_name) # To safely change the mouse cursor, the mouse must be over the window # these 2 functions help ensure we do it safely func set_mouse_cursor(cursor:compat_Cursor) -> void: if mouseOverWindow and windowFocused: activeCursor = cursor + activeCursor.currentFrame = 0 currentCursors[cursor.shape] = cursor queuedCursors[cursor.shape] = null + activeCursor.set_hardware_cursor(0) else: queuedCursors[cursor.shape] = cursor diff --git a/game/src/Game/GameStart.gd b/game/src/Game/GameStart.gd index bb6e2411..fdc6c019 100644 --- a/game/src/Game/GameStart.gd +++ b/game/src/Game/GameStart.gd @@ -108,8 +108,9 @@ func _load_compatibility_mode() -> void: if GameSingleton.set_compatibility_mode_roots(_compatibility_path_list) != OK: push_error("Errors setting game roots!") - CursorSingleton.load_cursors() + #CursorSingleton.load_cursors() CursorManager.set_prefered_res(Vector2i(48,48)) + CursorManager.load_cursors() CursorManager.set_compat_cursor("normal",Input.CURSOR_ARROW) #TODO: Change Busy to be shape Input.CURSOR_BUSY when done testing CursorManager.set_compat_cursor("busy",Input.CURSOR_IBEAM) From 27a29e94edce0e357921d67f6e0aab9a6984f435 Mon Sep 17 00:00:00 2001 From: Nemrav <> Date: Tue, 20 Aug 2024 15:19:19 -0300 Subject: [PATCH 5/9] improve temp cursor test code --- game/src/Game/Autoload/CursorManager.gd | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/game/src/Game/Autoload/CursorManager.gd b/game/src/Game/Autoload/CursorManager.gd index 729aed5a..1987d4e0 100644 --- a/game/src/Game/Autoload/CursorManager.gd +++ b/game/src/Game/Autoload/CursorManager.gd @@ -120,15 +120,10 @@ var cooldown = 0.0 func _process(delta) -> void: #TODO: Remove test code when done testing cooldown -= delta - var cursor_names = [ - "aero_busy", "drum", "aero_link_i", - "attack_move","busy","cant_move","deploy_not_valid","deploy_valid","dragselect", - "embark","exploration","friendly_move","no_move","normal","objective","selected" - ] if Input.is_mouse_button_pressed(MOUSE_BUTTON_RIGHT) and cooldown <= 0: cooldown = 0.3 - cur_ind = (cur_ind + 1) % cursor_names.size() - set_compat_cursor(cursor_names[cur_ind]) + cur_ind = (cur_ind + 1) % CursorSingleton.cursor_names.size() + set_compat_cursor(CursorSingleton.cursor_names[cur_ind]) #end test code #only attempt to update the mouse when this wont crash anything From 3ca827f8c2525751bfd16ac9795e634e9c865837 Mon Sep 17 00:00:00 2001 From: Nemrav <> Date: Tue, 20 Aug 2024 17:16:56 -0300 Subject: [PATCH 6/9] add argument names to cursor singleton functions --- .../openvic-extension/singletons/CursorSingleton.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/extension/src/openvic-extension/singletons/CursorSingleton.cpp b/extension/src/openvic-extension/singletons/CursorSingleton.cpp index 950990f2..d396409c 100644 --- a/extension/src/openvic-extension/singletons/CursorSingleton.cpp +++ b/extension/src/openvic-extension/singletons/CursorSingleton.cpp @@ -21,12 +21,12 @@ using namespace OpenVic::NodeTools; void CursorSingleton::_bind_methods() { OV_BIND_METHOD(CursorSingleton::load_cursors); OV_BIND_METHOD(CursorSingleton::get_frames,{"cursor name","resolution index"},"normal",0); - OV_BIND_METHOD(CursorSingleton::get_hotspots,"normal",0); - OV_BIND_METHOD(CursorSingleton::get_animationLength,"normal"); - OV_BIND_METHOD(CursorSingleton::get_displayRates,"normal"); - OV_BIND_METHOD(CursorSingleton::get_sequence,"normal"); - OV_BIND_METHOD(CursorSingleton::get_resolutions,"normal"); - OV_BIND_METHOD(CursorSingleton::generate_resolution,"normal",0,Vector2i(64,64)); + OV_BIND_METHOD(CursorSingleton::get_hotspots,{"cursor name","resolution index"},"normal",0); + OV_BIND_METHOD(CursorSingleton::get_animationLength,{"cursor name"},"normal"); + OV_BIND_METHOD(CursorSingleton::get_displayRates,{"cursor name"},"normal"); + OV_BIND_METHOD(CursorSingleton::get_sequence,{"cursor name"},"normal"); + OV_BIND_METHOD(CursorSingleton::get_resolutions,{"cursor name"},"normal"); + OV_BIND_METHOD(CursorSingleton::generate_resolution,{"cursor name","base resolution index", "target resolution"},"normal",0,Vector2i(64,64)); OV_BIND_METHOD(CursorSingleton::get_cursor_names); ADD_PROPERTY(PropertyInfo( From 23e5e3346ac5e1193a5ae50e4126aa8a114437a2 Mon Sep 17 00:00:00 2001 From: Nemrav <> Date: Sun, 15 Sep 2024 14:47:57 -0300 Subject: [PATCH 7/9] apply cursor code style fixes --- .../singletons/CursorSingleton.cpp | 41 +++++++++---------- .../singletons/CursorSingleton.hpp | 8 ++-- game/src/Game/Autoload/CursorManager.gd | 37 +++++++---------- game/src/Game/GameStart.gd | 8 ++-- 4 files changed, 43 insertions(+), 51 deletions(-) diff --git a/extension/src/openvic-extension/singletons/CursorSingleton.cpp b/extension/src/openvic-extension/singletons/CursorSingleton.cpp index d396409c..8c1d70bc 100644 --- a/extension/src/openvic-extension/singletons/CursorSingleton.cpp +++ b/extension/src/openvic-extension/singletons/CursorSingleton.cpp @@ -16,7 +16,6 @@ using OpenVic::Utilities::std_to_godot_string; using namespace godot; using namespace OpenVic; -using namespace OpenVic::NodeTools; void CursorSingleton::_bind_methods() { OV_BIND_METHOD(CursorSingleton::load_cursors); @@ -59,7 +58,7 @@ Array CursorSingleton::get_frames(String const& name, int res_index){ } TypedArray CursorSingleton::get_hotspots(String const& name, int res_index){ - if(res_index < cursors[name].images.size()){ + if(res_index < cursors[name].hotspots.size()){ return cursors[name].hotspots[res_index]; } return TypedArray(); @@ -73,18 +72,18 @@ TypedArray CursorSingleton::get_resolutions(String const& name){ return cursors[name].resolutions; } -TypedArray CursorSingleton::get_displayRates(String const& name){ +PackedFloat32Array CursorSingleton::get_displayRates(String const& name){ if(cursors[name].displayRates.has_value()){ return cursors[name].displayRates.value(); } - return TypedArray(); + return PackedFloat32Array(); } -TypedArray CursorSingleton::get_sequence(String const& name){ - if(cursors[name].displayRates.has_value()){ +PackedInt32Array CursorSingleton::get_sequence(String const& name){ + if(cursors[name].sequence.has_value()){ return cursors[name].sequence.value(); } - return TypedArray(); + return PackedInt32Array(); } void CursorSingleton::generate_resolution(String const& name, int base_res_index, Vector2i target_res){ @@ -128,7 +127,7 @@ String CursorSingleton::to_define_file_name(String const& path) const { bool CursorSingleton::load_cursors() { GameSingleton const* game_singleton = GameSingleton::get_singleton(); - ERR_FAIL_NULL_V_MSG(game_singleton, false, vformat("Error retrieving GameSingleton")); + ERR_FAIL_NULL_V_MSG(game_singleton, false, "Error retrieving GameSingleton"); static constexpr std::string_view cursor_directory = "gfx/cursors"; bool ret = true; @@ -194,19 +193,19 @@ bool CursorSingleton::_load_cursor_ani(String const& name, String const& path) { std::vector> hotspots_by_resolution; TypedArray resolutions; - TypedArray displayRates; - TypedArray sequence; + PackedFloat32Array displayRates; + PackedInt32Array sequence; //ani header variables - int numFrames; - int numSteps; - Vector2i dimensions; - int bitCount; - int numPlanes; //??? - int displayRate; //how long each frame should last - int flags; - bool iconFlag; - bool sequenceFlag; + int numFrames = 1; + int numSteps = 1; + Vector2i dimensions = Vector2i(1,1); + int bitCount = 1; + int numPlanes = 1; //??? + int displayRate = 1; //how long each frame should last + int flags = 0; + bool iconFlag = false; + bool sequenceFlag = false; while(file->get_position() < riff_size){ @@ -406,7 +405,7 @@ CursorSingleton::image_hotspot_pair_asset_t CursorSingleton::_load_pair(Ref(pow(2, bitsPerPixel)); + paletteSize = 1 << bitsPerPixel; } int importantColours = imageData.decode_u32(36); @@ -542,7 +541,7 @@ PackedByteArray CursorSingleton::_read_32bit_pixel(int i, int offset, PackedByte int CursorSingleton::_get_row_start(Vector2i dimensions, int y_coord, int bitsPerPixel){ int rowCount = dimensions.x * bitsPerPixel / 32; bool hasExtraRow = ((dimensions.x * bitsPerPixel) % 32) != 0; - rowCount += 1*hasExtraRow; + rowCount += hasExtraRow; //TODO 1* return rowCount*y_coord*4; //4 bytes per row * rows down } diff --git a/extension/src/openvic-extension/singletons/CursorSingleton.hpp b/extension/src/openvic-extension/singletons/CursorSingleton.hpp index f6911ba1..b7a498ce 100644 --- a/extension/src/openvic-extension/singletons/CursorSingleton.hpp +++ b/extension/src/openvic-extension/singletons/CursorSingleton.hpp @@ -43,8 +43,8 @@ namespace OpenVic { std::vector images; godot::TypedArray resolutions; int animationLength; //1 for static cursors - std::optional> displayRates; - std::optional> sequence; + std::optional displayRates; + std::optional sequence; }; //map of "subfolder/fileName.cur/.ani" -> cursor asset, subfolder comes after gfx/cursor @@ -90,8 +90,8 @@ namespace OpenVic { godot::TypedArray get_hotspots(godot::String const& name, int res_index = 0); int get_animationLength(godot::String const& name); godot::TypedArray get_resolutions(godot::String const& name); - godot::TypedArray get_displayRates(godot::String const& name); - godot::TypedArray get_sequence(godot::String const& name); + godot::PackedFloat32Array get_displayRates(godot::String const& name); + godot::PackedInt32Array get_sequence(godot::String const& name); void generate_resolution(godot::String const& name, int base_res_index, godot::Vector2i target_res); }; diff --git a/game/src/Game/Autoload/CursorManager.gd b/game/src/Game/Autoload/CursorManager.gd index 1987d4e0..897f7315 100644 --- a/game/src/Game/Autoload/CursorManager.gd +++ b/game/src/Game/Autoload/CursorManager.gd @@ -9,8 +9,8 @@ class compat_Cursor: var frames:Array var hotspots:Array[Vector2i] var is_animated:bool = false - var sequence:Array[int] = [0] - var timings:Array[float] = [1.0] + var sequence:PackedInt32Array = [0] + var timings:PackedFloat32Array = [1.0] #Cursor state var currentFrame : int = 0 @@ -53,9 +53,8 @@ class compat_Cursor: self.frames = CursorSingleton.get_frames(self.cursor_name,len(self.resolutions)-1) self.hotspots = CursorSingleton.get_hotspots(self.cursor_name,len(self.resolutions)-1) - assert(len(self.frames ) != 0) + assert(len(self.frames) != 0) - func generate_new_res(base_res_index:int, resolution:Vector2i) -> void: # resolution wasn't in among the default, need to generate it ourselves CursorSingleton.generate_resolution(cursor_name,base_res_index,resolution) @@ -88,7 +87,8 @@ var preferred_res : Vector2i = Vector2i(32,32) #Shape > Cursor dictionnaries #NOTE: In terms of V2, this is unnecessary, as the only cursor that isn't of shape #"arrow" in v2 is "busy", which needs to be manually triggered anyways like arrow. -#This is needed for shapes like IBEAM which get switched to automatically +# and so could be just left as an "arrow" +#This would be necessary for shapes like IBEAM which get switched to automatically var currentCursors : Dictionary = { Input.CURSOR_ARROW:null, Input.CURSOR_BUSY:null, @@ -158,36 +158,29 @@ func _process(delta) -> void: #if we didnt change cursors and are animated, do an update if activeCursor != null and activeCursor.is_animated: activeCursor._process_cursor(delta,advanceFrame) - + func set_prefered_res(res_in:Vector2i) -> void: preferred_res = res_in #override_other_queued is to stop an animation frame from taking precedence over #a cursor switch -func set_compat_cursor(cursor_name:String, cursor_shape:Input.CursorShape = -1) -> void: - #var cursor = compat_Cursor.new(cursor_name,cursor_shape) +func set_compat_cursor(cursor_name:String) -> void: if cursor_name in loaded_cursors: var cursor = loaded_cursors[cursor_name] - if cursor_shape != -1: - cursor.shape = cursor_shape cursor.set_resolution(preferred_res) - set_mouse_cursor(cursor) + queuedCursors[cursor.shape] = cursor else: push_warning("Cursor name %s is not among loaded cursors" % cursor_name) - -# To safely change the mouse cursor, the mouse must be over the window -# these 2 functions help ensure we do it safely -func set_mouse_cursor(cursor:compat_Cursor) -> void: - if mouseOverWindow and windowFocused: - activeCursor = cursor - activeCursor.currentFrame = 0 - currentCursors[cursor.shape] = cursor - queuedCursors[cursor.shape] = null - activeCursor.set_hardware_cursor(0) + +func set_cursor_shape(cursor_name:String, cursor_shape:Input.CursorShape) -> void: + if cursor_name in loaded_cursors: + loaded_cursors[cursor_name].shape = cursor_shape else: - queuedCursors[cursor.shape] = cursor + push_warning("Cursor name %s is not among loaded cursors" % cursor_name) +# To safely change the mouse cursor, the mouse must be over the window +# this function helps ensure we do it safely func _notification(what): match(what): NOTIFICATION_WM_MOUSE_ENTER: diff --git a/game/src/Game/GameStart.gd b/game/src/Game/GameStart.gd index fdc6c019..252b7507 100644 --- a/game/src/Game/GameStart.gd +++ b/game/src/Game/GameStart.gd @@ -108,15 +108,15 @@ func _load_compatibility_mode() -> void: if GameSingleton.set_compatibility_mode_roots(_compatibility_path_list) != OK: push_error("Errors setting game roots!") - #CursorSingleton.load_cursors() CursorManager.set_prefered_res(Vector2i(48,48)) CursorManager.load_cursors() - CursorManager.set_compat_cursor("normal",Input.CURSOR_ARROW) + CursorManager.set_compat_cursor("normal") #TODO: Change Busy to be shape Input.CURSOR_BUSY when done testing - CursorManager.set_compat_cursor("busy",Input.CURSOR_IBEAM) + CursorManager.set_cursor_shape("busy",Input.CURSOR_IBEAM) + CursorManager.set_compat_cursor("busy") setup_title_theme() - + if GameSingleton.load_defines_compatibility_mode() != OK: push_error("Errors loading game defines!") From 5e0d3d45a779c46458780e8616e1a07ee33b92d9 Mon Sep 17 00:00:00 2001 From: Nemrav <> Date: Sun, 15 Sep 2024 21:52:59 -0300 Subject: [PATCH 8/9] removed sound changes from cursors pr --- .../src/openvic-extension/singletons/SoundSingleton.cpp | 2 +- game/src/Game/MusicConductor/MusicConductor.gd | 5 +---- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/extension/src/openvic-extension/singletons/SoundSingleton.cpp b/extension/src/openvic-extension/singletons/SoundSingleton.cpp index 67fa71ce..a32d9fef 100644 --- a/extension/src/openvic-extension/singletons/SoundSingleton.cpp +++ b/extension/src/openvic-extension/singletons/SoundSingleton.cpp @@ -1,7 +1,7 @@ #include "SoundSingleton.hpp" #include -//#include +#include #include #include diff --git a/game/src/Game/MusicConductor/MusicConductor.gd b/game/src/Game/MusicConductor/MusicConductor.gd index 7aa8a6ef..7103aff1 100644 --- a/game/src/Game/MusicConductor/MusicConductor.gd +++ b/game/src/Game/MusicConductor/MusicConductor.gd @@ -95,10 +95,7 @@ func select_previous_song() -> void: func setup_compat_song(file_name) -> void: var song = SongInfo.new() var stream = SoundSingleton.get_song(file_name) - if stream == null: - push_error("Audio Stream for compat song %s was null" % file_name) - return - + var metadata = MusicMetadata.new() metadata.set_from_stream(stream) var title = metadata.title From 495e77ac1f42c131f71d826eddc3671a5c9ca236 Mon Sep 17 00:00:00 2001 From: Nemrav <> Date: Mon, 16 Sep 2024 14:00:22 -0300 Subject: [PATCH 9/9] change cursor names to stringname --- game/src/Game/GameStart.gd | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/game/src/Game/GameStart.gd b/game/src/Game/GameStart.gd index 252b7507..c7c4c130 100644 --- a/game/src/Game/GameStart.gd +++ b/game/src/Game/GameStart.gd @@ -110,10 +110,10 @@ func _load_compatibility_mode() -> void: CursorManager.set_prefered_res(Vector2i(48,48)) CursorManager.load_cursors() - CursorManager.set_compat_cursor("normal") + CursorManager.set_compat_cursor(&"normal") #TODO: Change Busy to be shape Input.CURSOR_BUSY when done testing - CursorManager.set_cursor_shape("busy",Input.CURSOR_IBEAM) - CursorManager.set_compat_cursor("busy") + CursorManager.set_cursor_shape(&"busy",Input.CURSOR_IBEAM) + CursorManager.set_compat_cursor(&"busy") setup_title_theme()