|
You should be able to do some relatively simple math to calculate what cell is under the cursor yourself, if you have to, based off the mouse position, camera offset, zoom level and tile size. I don't know anything about godot, so I can't tell you if something to do that already exists. edit: or you could just figure it out while I'm typing ZombieApostate fucked around with this message at 17:20 on Feb 8, 2023 |
# ? Feb 8, 2023 17:17 |
|
|
# ? Apr 26, 2024 17:13 |
|
I spent days wondering why it is so hard. I used the mouse coordinates inside the window. So in a 1920x1080 window mouse coords could be 0,0 or 1920x1080 or something in between. Accidentally while browsing reddit I found about the global mouse coordinates. In a larger tilemap the coords can be like 8000,8000 so no wonder everything was always off. Edit: another very interesting thing is to figure out how to use a variable texture from a TileSet in a 2d sprite. Or maybe I need to create a tilemap and use set_cell and uhh attach the tilemap to a mouse while the mouse is moving around.. Ihmemies fucked around with this message at 17:54 on Feb 8, 2023 |
# ? Feb 8, 2023 17:26 |
|
what tf kind of syntax does unity want me to use to pull a list from one class to the other? I have a list in one class. i want to add/remove to it from a different class. its public. every attempt to reference it either throws a bunch of errors about monobehaviors not being able to be called as new, or null reference exceptions, or some kind of error.
|
# ? Feb 8, 2023 22:57 |
|
Raenir Salazar posted:Sadly it's not that simple since at work we have like 50 modules and I've been tasked with refactoring some code to be less tightly couples and I think for the observer pattern ideally I should be passing a reference to the subject of all of the observers that will be watching it? And I'm trying to find a spot that would be persistent between levels. Seems to indicate the GameInstance is created and initialised first. Edit: though on closer inspection it mentions UWorld Start but not the UWorld init. For observer you'd be using delegates and binding to them in every other relevant place right? jizzy sillage fucked around with this message at 23:10 on Feb 8, 2023 |
# ? Feb 8, 2023 23:08 |
|
AA is for Quitters posted:what tf kind of syntax does unity want me to use to pull a list from one class to the other? there's nothing special going on in unity, it's standard c# and you do it like you would anywhere else without code and only vague references to the errors you're receiving it's impossible to tell what you're doing wrong maybe you're trying to use a non static variable without a reference? maybe you've loaded a new scene and your gameobject was destroyed so your reference is gone? there's basically endless ways to do it wrong
|
# ? Feb 8, 2023 23:13 |
|
Basically i'm doing ragdoll bowling. so this is class A code:
code:
while there is absolutely no *need* for Pinsetter to be a monobehavior, unity doesn't wanna let me attach it to a component without it being one. the private list in class B is a leftover from bashing my head against the wall trying to figure out how to pull this reference over. the goal is so that when the ball hits part of the ragdoll, the ragdoll collapses (this part works just fine), and then gets removed from the list so that when i start the second ball those pins don't come back down. Dr Jankenstein fucked around with this message at 04:22 on Feb 9, 2023 |
# ? Feb 9, 2023 04:13 |
|
You cannot create a MonoBehaviour using new! You can instantiate a GameObject that already has the script attached to it: code:
code:
code:
code:
full point fucked around with this message at 07:17 on Feb 9, 2023 |
# ? Feb 9, 2023 07:15 |
|
If you (quite understandably) want to create and initialize a monobehavior in a single step, I recommend using a factory method to do it. e.g. if I have a Gun behavior and it needs to know about the AmmoTracker behavior so it can tell it when bullets are used, I would add this function to the Gun class:code:
Or you can just do the setup code you wanted to do in Awake or Start inside of the factory method instead.
|
# ? Feb 9, 2023 16:23 |
|
This is what i'm running into. exposing the pinsetter game object to the inspector, and dragging it in to the joint script, so that i have code:
code:
code:
that's why i'm saying i'm missing something about how c# wants the syntax to access that list. StackOverflow keeps saying the same thing you are that I should be able to get to it like that. I'm at that weird stage of learning to code where its like, i understand *what* i need to do to make the program do what i want it to, but am still learning *how* to make it do that.Like i feel like I'm missing a *super* obvious step about how to call a reference at an earlier step in the process and just...have never had it be an issue until this point.
|
# ? Feb 9, 2023 16:35 |
|
jizzy sillage posted:
Yeah that last part is where I'm a little confused. Problem: We have a settings menu that is managing the internal state of various I suppose I'll call them "Models" so the Settings Menu is calling methods like ChangeCameraSpeed on our SettingsManager. Example: code:
The solution as I understand it: Use the Observer pattern, which in Unreal is/can? implemented by using Delegate/Events. Instead of the IObserver/ISubject boilerplate which I see on C++ Websites? However, while we do have an EventManager class that contains something like 95% of our games delegates, but it's also 1,000 lines and to use events for all the methods the Settings Menu currently has would probably add another 15 events; and if more parts of the game need similar refactoring I think it'd be better if we didn't continue to bloat this single "Superman" Event class? So if I want to avoid using this global event class, I just create the delegates for the Settings Menu; and then broadcast; but then the Settings Manager, in order to subscribe to these events, would still need an include and thus needs to still "know about" the Settings Menu class right? Is that normal? Am I overthinking this and is it fine for the "Observer" in this case to know about the thing it wants to Observe? In general when googling/researching design patterns I get that there's like a hierarchy of concerns of when it is or is not called for but I'm not sure how to judge/determine this.
|
# ? Feb 9, 2023 16:45 |
|
Since the building placement is hard to implement, I've been toying with worldgen class. Now it can read an input image file and generate land and water based on pixel colors. First try did not go too well: I adjusted the tile placement but then found out that the input map has features which are hard to render with the used tileset.. like water tiles surrounded by land in 3 sides: I implemented a smoothing pass which goes over the 2D map array and recursively looks for bad spots and replaces them with ground. It now looks acceptable: A 1000x1000 map causes 360000 recursions. When zoomed out the FPS is under 20 when all the million tiles are visible. Some kind of LOD system would be handy, or something. Seems big tilemaps aren't meant to be looked from far away: I made a bunch of methods which do their thing one by one. Next I'm interested in trying to generate some forests and bogs procedurally. No idea how to even start. I have implemented a flood fill algorithm once, but it's hard to imagine for it to produce nice results. Well, maybe I'll at least try it. Also the mapgen now takes quite a few seconds even with only a 1000x1000 map, I need to probably implement saving&loading of map data so it doesn't have to do it every time I start up the game.
|
# ? Feb 9, 2023 20:04 |
|
AA is for Quitters posted:
You can't add a component to GameObject, that's just a type of object. In a monobehavior you can refer to gameObject, which will refer to the instance of GameObject the script is attached to.
|
# ? Feb 9, 2023 20:05 |
|
GetComponent does the same thing... should there be a method that *returns* the list in the pinsetter class in order to call it? I havent seen a return in any of the similar questions to mine on stack overflow, but is that something that's just getting left out because its just something that is assumed dumbasses like me should know about? Like that's why i think i'm missing something really obvious with how to reference other classes in C# and just prior to this point I've not run into issues with it.
|
# ? Feb 9, 2023 20:49 |
|
AA is for Quitters posted:GetComponent does the same thing... should there be a method that *returns* the list in the pinsetter class in order to call it? I havent seen a return in any of the similar questions to mine on stack overflow, but is that something that's just getting left out because its just something that is assumed dumbasses like me should know about? Like that's why i think i'm missing something really obvious with how to reference other classes in C# and just prior to this point I've not run into issues with it. It seems to me like you don't understand the difference between types and objects that are instances of a type. You aren't calling a method on the GameObject class, but on an object that is GameObject. Instead of code:
code:
There are also methods that are called directly from classes. These are called static methods. GameObject.Find, for example, is a static method in Unity.
|
# ? Feb 9, 2023 21:54 |
|
Peewi posted:You aren't calling a method on the GameObject class, but on an object that is GameObject. The example I usually reach for is "You're trying to touch the ontological concept of Honda Civic, instead of say, 12 rats tied together's Honda Civic, year 2008, vin #12345, with 168,000 miles on it, and half a tank of gas"
|
# ? Feb 9, 2023 22:11 |
|
12 rats tied together posted:The example I usually reach for is "You're trying to touch the ontological concept of Honda Civic, instead of say, 12 rats tied together's Honda Civic, year 2008, vin #12345, with 168,000 miles on it, and half a tank of gas" This is..really helpful in understanding the difference, but like, is there a better primer for explaining it in terms of C#? Like, i get that i need to reference *that specific list* and not just, the concept of lists, and i need to do it from *that specific object*... i need this Honda Civic that is sitting in *that* parking lot at 228 Spruce St...i dont need to create a new parking lot, nor do i need to create a new honda civic... but I am not understanding how c# and unity reference that specific parking lot. like with the example, I have no loving clue what variable that is supposed to be getting added to... code:
|
# ? Feb 9, 2023 22:41 |
|
You're going to get frustrated trying to jump straight in without grasping the basics. I highly recommend getting a book designed for beginners and working your way through it. I found the Head First series from O'Reilly to be a great starting point when I was learning Java, they have one for C# that I imagine is similarly good: https://www.oreilly.com/library/view/head-first-c/9781491976692/
|
# ? Feb 9, 2023 22:47 |
|
AA is for Quitters posted:This is..really helpful in understanding the difference, but like, is there a better primer for explaining it in terms of C#? I can't help but I wanted to commiserate with you, I find c# to be almost entirely unreadable even in the best scenarios (which game code is usually not). I can do it in python for you though: Python code:
C# code:
- Create a thing called 'yourThing' which is 'of type GameObject' (it's appropriate to refer to the ontological concept of GameObject here). - The value of yourThing should be whatever happens when you call "GameObject()" (which we know from the documentation link is a constructor that returns an instance of a GameObject) - Afterwards, take this thing and run its AddComponent method, with whatever "<Pinsetter>" means, which frankly I have no clue about because I can't read this language. 12 rats tied together fucked around with this message at 23:00 on Feb 9, 2023 |
# ? Feb 9, 2023 22:57 |
|
Do you guys have any ideas how to implement biome generation in a 2d map? To get some “natural looking” forests and bogs? What kind of algorithms would be useful, or must one go full dwarf fortress and simulate the world gen from beginning of time? Thanks https://gamedevacademy.org/procedural-2d-maps-unity-tutorial/ Edit: apparently by generating a noisemap and applhing biomes based on the output. Ihmemies fucked around with this message at 23:08 on Feb 9, 2023 |
# ? Feb 9, 2023 23:00 |
|
AA is for Quitters posted:like with the example, I have no loving clue what variable that is supposed to be getting added to... What's going on is your instance of myGameObjectVariable (which is presumably an instance of the GameObject class) has a method called AddComponent<T> that when called, creates a new component of type T (in this case, Pinsetter), and adds a reference to that component to the game object instance. So what has happened is the method mutated the instance myGameObjectVariable to now have an additional component attached to it. That method also returns the reference to the new component it just created, so because you are assigning it to the variable pinsetter, you get the same reference that was added to the game object. In other words, that code does the same thing as code:
|
# ? Feb 9, 2023 23:08 |
|
Raenir Salazar posted:Yeah that last part is where I'm a little confused. In the case of SettingsMenu making changes that only SettingsManager needs to know about, the coupling is okay - as long as the SetMan isn't watching for updates in its Tick. So if SetMenu only ever tells SetMan "Hey the player just updated their mouse sensitivity" then that's fine. You start to need Delegates (Unreal Engine Delegates) when you have a bunch of things all reliant on knowing certain things happened. Easy example is Player death. Bad: - Player dies - EventManager watches on Tick for bIsPlayerDead = true, triggers something - AudioManager watches on Tick for bIsPlayerDead = true, plays some audio - Enemies nearby all check to see if player is dead, find new targets - UI Widgets A, B, and C all watch and update. - etc. Good: - Player dies, Broadcasts Multicast Delegate OnPlayerDeathDelegate - EventManager is subbed to OnPlayerDeathDelegate, triggers OnPlayerDeath and records the stats, gives some achievements, whatever - AudioManager is subbed, plays some audio - Fifteen different enemies are nearby, subbed, get told the Player has died and find new targets. - UI Widgets A and B are subbed, so they get the broadcast and update, but turns out C never needed to care so we just removed the binding in the UI Widget C. The other aspect is that you're moving the code from one side of the equation to the other - instead of SettingsMenu calling SettingsManager->ChangeSensitivity(float newSense), now SettingsManager just binds itself to the delegate on construction and unbinds on death. Now in your SettingsMenu, you just blindly broadcast into the void "Hey a thing changed, anyone care? No? Cool." and it doesn't matter if nothing cared, or the SettingsManager hasn't finished Init, or there are five SettingsManagers of different types (one for Gamepad, one for KB+M) with different behaviours that happen when the broadcast is sent. By moving all the binding into the SettingsManager, it can bind to the Menu events, or it can bind to a system that Loads/Saves settings, or both, and you don't need the SettingsMenu and LoadSaveManager to have a reference to a SettingsManager. Bit of a braindump but that might help a little?
|
# ? Feb 10, 2023 03:20 |
|
Ihmemies posted:Do you guys have any ideas how to implement biome generation in a 2d map? To get some “natural looking” forests and bogs? What kind of algorithms would be useful, or must one go full dwarf fortress and simulate the world gen from beginning of time? Thanks Yeah, one way or another you're going to be relying on noise. Perlin or simplex, you should be able to find libraries pretty easily. Find something that has a way to preview the different settings, because it takes awhile to get a handle on what the different variables do. Procedural content generation is all about iteratively trying poo poo and seeing what happens.
|
# ? Feb 10, 2023 04:02 |
|
Seems Godot has a built-in support for generating noise textures with multiple types of noise: https://docs.godotengine.org/en/latest/classes/class_fastnoiselite.html I have to look at this. There was also a tutorial how to generate the tex with code and read the noise texture's values and generate different tiles based on it: https://www.youtube.com/watch?v=m6mu4uPGrMk I wonder if I could overlay the noise texture to my map image file/texture's land parts.. that way I could read the data from image only once, and generate different terrain straight from the data. Alllso this mapgen is so very slow. I have to time it and see which operations take most of the time: https://www.gdquest.com/tutorial/godot/gdscript/optimization-measure/ Of course the profiler doesn't really work for me. Also the get_ticks_usec() isn't supported in godot 4 anymore. Edit: it's Time. instad of OS. And results are: pixel-data 0 smooth land 0.652519 generate biomes 0.000001 set tilemap tiles 2.179979 While the game takes at least 10-15 seconds to start. So I wonder what the the game is actually doing with the time. Well at least the mapgen is fast enough, I’ll just implement a loading screen. Edit2: now I’m wondering how to generate textures based on TileMap data for zoomed out views. Displaying big TileMaps on screen tanks the fps to sub 20. Or maybe I need to do “mipmapped” tilemaps with 1 or 2 or 4px resolution and switch to a scene rendered with the low res tilemap after certain zoom level. Edit3: or maybe I have to render only a portion of the tilemap based on the game state, and zoom, and switch to static textures generated based on game data.. or.. uh hmm.. drat. So many options, and no idea which of them are good. Ihmemies fucked around with this message at 09:02 on Feb 10, 2023 |
# ? Feb 10, 2023 08:14 |
|
Implementing biomes based on noise data was extremely straightforward. Next I have to: - refactor my forest edge/shoreline drawing code because it is buggy and bad. - implement a bounds checking method which several other methods can use Python code:
Ihmemies fucked around with this message at 16:33 on Feb 10, 2023 |
# ? Feb 10, 2023 16:27 |
|
Ihmemies posted:Implementing biomes based on noise data was extremely straightforward. Next I have to: I wanna fly an airship over that
|
# ? Feb 10, 2023 16:51 |
|
Chainclaw posted:I wanna fly an airship over that Well I could try to implement a player with an airship sprite, which can look/move in 8 different directions... It will probably be a good exercise Altough my aim wasn't to make a player-driven game. Hmm, maybe I could implement cars/trains/walking citizens at least, I think simcity 4 had them? It might be an interesting add-on, if I ever get the game part implemented. After building a city, just walk/fly around and chill. Hmm.
|
# ? Feb 10, 2023 16:54 |
|
Now I wish I understood tilemaps in Godot better. If I create 16 new tilemaps with TileMap.new() and put them to a list, and use set_cell() to fill one of the tilemaps. How do I actually display one of those generated tilemaps on screen with gdscript? Where does the tilemap go if I display it on screen? How I can tile tile tilemaps so they don't overlap? For example if I have 16 tilemaps I want them positioned like so on screen: [01] [02] [03] [04] [05] [06] [07] [08] [09] [10] [11] [12] [13] [14] [15] [16] Then I want to individually show and hide/clear the tilemaps when player/camera/whatever gets close enough that the tilemap will soon be on screen. I think I can manage that, but how to actually render even one tilemap on screen.. Do I need to create a canvaslayer? Add the tilemap array as a child? ? I really have no idea. All the examples are again GUI based which don't actually show how things are done. Edit: maybe like that. Have a world node and add chunk scenes as a child to it... https://github.com/NesiAwesomeneess/ChunkLoader/blob/main/ChunkLoading/World.gd Ihmemies fucked around with this message at 13:17 on Feb 12, 2023 |
# ? Feb 12, 2023 12:57 |
|
roomforthetuna posted:I guess your way avoids writing paths ever, but it's weird because you're having to make a variable name to substitute for the path, at which point you *could* just put the node in the root then it's got a short path so it's kinda the same but with less boilerplate code?
|
# ? Feb 12, 2023 15:47 |
|
I really wish I understood something about godot. I have a Chunk class which extends Tilemap. It is visible in node tree with the is_visible_in_tree() command. If I put a func _draw(): draw_circle(Vector2(x,y), 350.0, Color(0,0,0)) to my Chunk class, it draws a black circle with 350px radius. Then I have a chunk generation method like this: Python code:
Edit: Even the tiles look reasonable.. var str = "%s %s %s" print(str % [tile_data, row, col]) code:
Edit: Finally I realized it is not using a tileset! ! ! ! ! ! code:
Edit2: Edit3: And now it loads chunks in n tile range (depending on vertical resolution) around the camera position, and frees chunks which are not needed anymore for display. Next I can tackle the save/load system. Probably best would be to save the generated maps, since 4096x4096 cell mapgen takes a minute. Then when a player starts a new game on a pre-generated map, just save the map data + changed cells + other new/changed game data to a new save file.. or something. Then the player could resume the game quickly, when the game doesn't need to re-generate the map, and can draw chunks dynamically. That way even a large size map should load and be playable nearly instantly. Ihmemies fucked around with this message at 22:23 on Feb 12, 2023 |
# ? Feb 12, 2023 17:02 |
|
I figured out my variable issue was due to never actually like, assigning variables at runtime, only declaring the defaults. So it was trying to, obviously, pull a null reference. but now i have another *really* dumb question. Any way to hide cautions in unity? It's hard to go through the console because i'm using a font that doesn't have an underlined version for a UI thing.
|
# ? Feb 13, 2023 06:27 |
|
Any idea how I should use a thread to call chunk generation methods? https://docs.godotengine.org/en/latest/tutorials/performance/using_multiple_threads.html code:
Python code:
1) create a new thread 2) new chunk coordinates are added to the chunk_queue list's front 3) thread keeps looking at the list, and if the list has items, it takes the last one and generates a chunk to the coords provided by the list item Edit: I figured part of it out. The thread started before game loading was ready, so it gummed up the thing. Now the chunk handler won't do anything until the world gen is ready. The thread works, kind of. Chunk queue fills up but the thread doesn't get any chunk generation done. Time to figure it out next... no children (new chunks) get added to the chunk handler. Edit: now it maybe works. It is horrible, but it was the only example I understood something about : https://pastebin.com/N3WNeRUJ Ihmemies fucked around with this message at 20:38 on Feb 13, 2023 |
# ? Feb 13, 2023 16:49 |
|
AA is for Quitters posted:I figured out my variable issue was due to never actually like, assigning variables at runtime, only declaring the defaults. So it was trying to, obviously, pull a null reference. I don't know if it's an extension I am using or not, but there's some icons I have towards to top right of the console that let me click warnings and errors on and off.
|
# ? Feb 13, 2023 17:52 |
|
Ihmemies posted:Any idea how I should use a thread to call chunk generation methods? I like that you're going through some similar pains I was. I believe Sebastian Lague has a tutorial series on procedural world generation using multithreaded chunks, its for Unity3D though so it might not be immediately useful to you though. https://www.youtube.com/playlist?list=PLFt_AvWsXl0eBW2EiBtl_sxmDtSgZBxB3
|
# ? Feb 14, 2023 01:31 |
|
Ihmemies posted:
It's also not doing it with any mutex locking, which means potentially if the update to chunk_queue.size() happens on a different core, it won't even see that anything changed. The common ways to resolve this are: 1. Some sort of mutex.await construct that makes it only check the value when the mutex is released elsewhere, and only take the mutex if the condition is met. This fixes the busy-looping, but still leaves the thread blocked until something happens, so you wouldn't want to do it on a main thread, but it's a good solution when a worker thread needs to wait for something. You might want to use this form for your worker thread that performs load operations. 2. A callback dispatcher. The worker thread pushes a callback onto the dispatcher when a piece of work completes, and the main thread (or a worker, anyone who listens to that dispatcher) does the work in that callback when it gets around to it. This usually works well for games and similar "mostly busy" tasks. The thing you ended up with resembles a callback dispatcher. You can either wait for the dispatcher to receive work (again with something like mutex.await), or peek at it once per frame or whatever to see if anything needs doing.
|
# ? Feb 14, 2023 03:50 |
|
roomforthetuna posted:In general you don't want to be doing a "while true" loop because now you have a thread just *constantly* checking if chunk_queue.size() > 0. Thanks. I switched to a semaphore.wait() inside the while true: -loop. I understand the loop execution stops, until something posts semapore.post() command elsewhere. Then the loop proceeds. Maybe that is enough. I did not achieve results with a 2nd thread which generates chunks, I mean the game runs a bit choppily while moving the camera, frametimes are not smooth. I will try other things next, like making a minimap. One weird thing is that when I set image pixels to brown, they display as white. If I set them as pink, they display as pink. I don't understand :-F At least drawing a minimap was easy, now I need to figure out the colors... also the game has a billion different image formats, I guessed a RGB8 would be good, maybe not then. Maybe it needs to be RGBF...
|
# ? Feb 14, 2023 07:51 |
|
Raenir Salazar posted:I like that you're going through some similar pains I was.
|
# ? Feb 14, 2023 09:07 |
|
Really I don't understand how Godot's images work either. I have a new image and I set each pixel with colors like image = Image.create(Globals.map_size, Globals.map_size, false, Image.FORMAT_RGBAF) color = Color(148, 113, 71) image.set_pixel(x, y, color) Then I turn it to texture so I can use it on sprite: minimap_texture = ImageTexture.create_from_image(image) sprite.texture = minimap_texture Result-> WHITE. HOW CAN IT BE WHITE. It does not make any sense at all to set a pixel's color as brown, and get white!!!!
|
# ? Feb 14, 2023 09:17 |
|
Ihmemies posted:Really I don't understand how Godot's images work either. I have a new image and I set each pixel with colors like I don't know anything about godot, but usually when you have image format float (I assume RGBAF is either 32 of 16 floats), white color will be (1.0, 1.0, 1.0), so if you set all three colors above 1 it will be white (if your green is anything other than (0,???,0), you can disregard this garbage theory)
|
# ? Feb 14, 2023 09:31 |
|
commando in tophat posted:I don't know anything about godot, but usually when you have image format float (I assume RGBAF is either 32 of 16 floats), white color will be (1.0, 1.0, 1.0), so if you set all three colors above 1 it will be white (if your green is anything other than (0,???,0), you can disregard this garbage theory) Well thanks. I have never ever seen colors being defined as floats between 0.0...1.0 anywhere before But I guess it makes sense in some way. Seems I have to use Color8(200, 150, 45) if I want to use human-readable formats without breaking out the pocket calculator or doing stupid poo poo like 1/255*200 when defining colors. Edit: also learning C# would help to get rid of this annoying editor in Godot which is extremely barebones. With C# I could just use VSC or something modern instead. This doesn't even have splitscreen?! Ihmemies fucked around with this message at 09:48 on Feb 14, 2023 |
# ? Feb 14, 2023 09:42 |
|
|
# ? Apr 26, 2024 17:13 |
|
Ihmemies posted:Well thanks. I have never ever seen colors being defined as floats between 0.0...1.0 anywhere before But I guess it makes sense in some way. Seems I have to use Color8(200, 150, 45) if I want to use human-readable formats without breaking out the pocket calculator or doing stupid poo poo like 1/255*200 when defining colors. Yeah no splitscreen. I miss my vim keybindings too. The good news is that they made sure not to architect Godot 4 such that implementing splitscreen would be an ordeal. I don't know if it's already in or not, but the plan is to have that in 4.
|
# ? Feb 14, 2023 09:53 |