WHERE WE'RE GOING WE DON'T NEED EYES
Yal
Creator of
Recent community posts
It was quite the experience back when GM 2023.8 dropped (with the big 'assets are a reference type instead of a regular number now' language overhaul) and I had to play through all my engines to make sure nothing had been broken, I'd forgotten I made a lot of this stuff. Like, why didn't I actually do something cool with these things myself at some point...? There's not enough hours in a day...
Actually, I just realized there's an even better way to solve it - moving the "event_user(0)" from the Create event to the Room Start event (which runs after all create events have finished), that removes the creation order dependency entirely! Just cut and paste it into the indicated location:
I'll update the base download with this change as well so more people can avoid suffering.
It's probably the order the Create Events run. The new button gets added at the end of the list, so it's not finished setting up when the keyboard menu controller's Create Event runs (it finds the currently hovered menu button by checking their "menu grid position" variables - but they don't exist until after the button objects have been properly created). You need to open the room's "Instance Creation Order" (there's a button in the inspector) and reorder things so the controller is created after all the buttons:
(There's a comment about having to do this in the create event of that object, but I maybe should've put it somewhere more visible...)
- Sunshine: The tunnel between the Water Tower and Waste Canal: if you make your way from the start point to the left wall without touching any grates, a new door will appear
- Raccoon: secret path close to the main base, there's a building where invisible blocks appear when you jump into them from below which lets you climb up a building into a secret area.
- Nadia: the misty cave, one of the long corridors has a fake wall on the left
- Vanessa: In the sky area, you can get out of bounds on the right-hand side
- Baron: in the grass dungeon in a secret room. Fake wall, path is marked with different blocks
It might be impossible to collect that Lore if you didn't do it the first time you took the girls through the Canal (and didn't obtain the alternate upgrade path for the rocket launcher, which is a missable event that happens the first time you go through the canal)
The shopkeeper should still be interactible when he shows up near the truck at the end, he's supposed to give you hints about the second upgrade for the Handgun. I'll have to look into this.
Even on my PC it gets slowdowns if you can survive long enough, but the main performance bottleneck is enemy-to-enemy collisions (where enemies are moved outside each other so you don't end up with a single 'conga line' blob) so you could get around that by focusing more on fewer stronger enemies instead of tons of weak enemies, or maybe change things around so each individual enemy object only collides with that one object, rather than the parent_enemy colliding with parent_enemy, so individual hordes keep their shape but they just phase through other groups.
The "a" "b" "y" puzzle is intended to flash all the letters at the same time when it's active, but there's a bug where they get out of sync if they go offscreen. If all of them are active you've solved the puzzle. (It's actually lower-case alpha, beta and gamma greek letters)
The blue lightning blocks are currently not breakable. They're intended as alternate paths for a second playable character that I never had time adding to the game.
There are some unfinished paths but there's 5 hidden easter egg characters, 10 hidden weapon upgrades (one for each weapon, Handgun and Fairy Pistol have two each), 50 total Lore and 2 alternate outfits to unlock.
No problem, glad it worked out so quickly!
Dot product is a very simple operation, it's just the sum of every vector component multiplied with the same component of the other vector (so the 2D case is 2 multiplications and 1 addition), so it should be faster than e.g. doing a geometric solution with trigonometry most of the time. The only thing it really does is tell you how parallel two vectors are, but it's the best choice if you need that exact thing!
I tested most of my engines with 2023.8 to make sure nothing had broken, I only found a single issue (a missing sprite_exists check in the open world 3D engine). array_length_1d is deprecated but trivial to replace (array_length does the exact same thing) so I figured there's no rush, there's currently no easy way to find a list of all deprecated functions in use in a project so I'd probably just miss something, think the project is safe and then run into a massive tech support nightmare when a new GM update drops anyway.
As for the for loop variable name, I'm just using "c" because there's a programming language called "C++" so writing "c++" as the increment operation is funny. That's the whole reason :P
Anything declared with "var" only exists in the piece of code it belongs to, so c's in different scripts will not affect each other. And with multiple loops in the same script, you reset the variable to 0 at the start of the for loop, so it doesn't matter it's reused. (The exception being when you have a loop inside a loop - then you need separate variables so the inner loop won't mess with the outer loop).
The gist of it is that the Z value is obtained by doing a bilinear interpolation between the Z values of the 4 corners. (TLC is short for Top Left Corner, and so on). It works with rotated things as well, but the code is simplified since I'm abusing the fact everything is parallel to the X and Y axes.
Even if you rotate an object sprite_width will be its width and sprite_height will be its height, so I think the issue is that you're not computing xx/yy in "rotated space". So you'd need some code along these lines:
(where "terrain" is the ID of the slope object you want to collide with, and "player" is the player object... I don't have the source code at hand right now so I've forgotten what names are actually used in the collision logic, lol)
(The way this works is, dot product basically multiplies two vectors taking their relative alignment into account, so when one vector has length 1 the dot product will 'project' the other onto its direction... so we use this property to turn the regular x/y coordinates into coordinates relative to the rotated slope.)
The other thing that might be needed here is to make sure collisions only happen when the player is over the slope object even when it's rotated, by default Game Maker will just scale up the bounding box when you rotate something (so it is a rectangle that fully encapsulates the sprite after applying rotation) so you might trigger collisions just by being near the rotated slope since you technically collide with it.
You specifically need to have the slope object's sprite use the "rectangle with rotation" collision mode (since it is a rectangle you rotate, this is the fastest option) and probably use the "precise" flag when doing the collision checks as well - I'm not 100% sure if the rotated rectangle collision mode counts as precise or imprecise but I think it's precise.
I bought a license for Mixcraft 9 RS a couple years ago and I composed all the music in that. (It's not the latest version of Mixcraft but it's good enough for me)
Composition-wise I mostly stuck to a single key per track (and a lot of the tracks are in C Major to be more upbeat) with a repeating, constant 4-bar bassline riff through the entire song, but with as much stuff going on as possible in those 4 bars to make it less monotonous. (Surgical Gardening or My City Is Sinking More Than Expected is probably the best example of this strat). I kinda feel like I fell into a trap of building every song the same way though, the first segment is always just the drums + bass and a lot of songs end with me removing everything except the drums so it'd be easier to make the song loop...
I'm reusing some instruments in a lot of tracks to add cohesion through the soundtrack (almost everything uses the same bass and drum kit VSTs for instance). I think I could've gone further here and actually settled on some sort of "musical identity"... everything is kinda just random stuff with the only thing in common being that they should make you wanna move fast, and I didn't put much more thought into it than that.
I generally composed the music before I made the stages and worked very fast due to the jam time limit (making like 3 songs per day), there's a bunch of discarded songs that were too bad to use as a result (and even with the stuff that made the cut, I'm a bit unhappy with the quality - the song that plays for like 10 seconds at the start of the cruise ship mission for instance). Maybe having a main theme and making every cutscene song a remix of that would've helped here...
Glad you figured out SOME way of opening it at last, at least... in the worst case, it's always possible to manually copy code from the GM Beta and recreate objects in the main project (should be a lot easier with two instances of GM so you can actually compare stuff properly, vs doing it by hand from the .gml / .yy files)
(The one thing that's not super obvious is that I use the GUI "Variables" interface for all the enemy stats, they're not set up by code in the Create event)
As I said, you can have both LTS and a regular version installed at the same time without issues, they show up as different programs entirely. So no need to abandon LTS entirely.
LTS was released in late october 2022 (version number of the current LTS download is 2022.0.1.31 so it still appears to use the same version!) so it's about 10 months old at this point - it's lagging behind on features quite a bit and the timing of the new project format was quite unfortunate. I couldn't see anything about a new LTS release on the roadmap so presumably the next LTS release won't happen until the 2 years of support has passed? (in ~14 months from now) Scratch that, an upcoming update to LTS for the end of this month was announced today. No info what features from the bleeding edge will be ported over to LTS yet though...
In the worst case I suppose I could send you all the code as text files or something so you can paste it into a fresh project, but it'll be a pain to reassemble all the events and object relationships so I wouldn't really recommend this approach...
For up-to-date information on this stuff (LTS stability / project format queries) it's probably easiest to check in on the official forums, there's a shortcut to open the GMC in your browser from within Game Maker (Help --> Game Maker Community). I used to be a moderator but I've never been involved with actual GM software development so I have very little knowledge about how it works under the hood.
I tried it out in the latest version and I can't reproduce it, no errors neither when closing the game window, nor when loading a different project or closing GM. Did it happen before you made those modifications?
Did some research on that error message and it seems to happen if you attempt to make a loop between two surfaces, like drawing a surface while it's set as the render target (and a common way to trigger it is having old surface IDs that aren't cleaned up, so that they later refer to new surfaces due to ID reuse). I think a good starting point for debugging what's happening is adding
show_debug_message(view_surface_id[0]) show_debug_message(view_surface_id[1]) show_debug_message(view_surface_id[2]) show_debug_message(view_surface_id[3])
to the Post-Draw event of obj_viewcontrol. If you see multiples of the same surface index here (other than -1 which means "not in use") it means something goes wrong.
If the problem only happens when you terminate the game, I think you could hack your way around it using draw_enable_drawevent(false) triggered in the event that closes the game, but before you do the actual closing procedure - if you aren't going to draw anything anyway, you can turn off all the draw events before you start freeing up surfaces and weird things happen with the IDs.
My guess is that it's created in a version newer than the LTS and something in the format has changed (I'm always on the latest version to find and fix issues with new features breaking my engines ASAP).
I know the new project loader was introduced recently-ish (version 2023.6, if memory serves) so potentially that's the issue, though the new format was made to be backwards-compatible so you'd think LTS could handle it... but it's almost a year old now so it might be lagging behind a bit...?
I asked around a bit and it sounds like you can have an LTS version and a regular version installed at the same time (as long as they're installed in different locations) so that could be a solution - having bleeding-edge for this engine and LTS for your other projects. (And having a beta and a regular version installed at the same time 100% definitely works)
It's pretty much become a genre at this point, hasn't it? I used to believe innovation stopped being a thing recently, we'd already threaded all the possible ground on gaming, so it's pretty cool seeing history happen before our own eyes.
I wasn't really planning on making this engine originally (conceptually it's not very challenging stuff going on behind the scenes) but I experimented a bit with making my own game for a jam and figured "heck, I've got some cool code here, might as well clean it up a bit and turn it into an asset". The biggest challenge really is GM's performance, the number of enemy-to-enemy collisions increase exponentially as the game goes on and it makes things go from smooth to unplayable very quickly once you hit the sweetspot.
1: There's a 'user' field for enqueued actions, for instance the a_user variable in the ctst_TURNQUEUE state in obj_battlecontrol's step event. (this state is probably when you want to consume the points, since it's when the action actually happens). This "user" field is a battlemonster object ID so simply check its side variable: if it's equal to side_ENEMY it's an enemy and shouldn't consume points.
2: Correct, enemies just pick a random move this way (add all valid actions to a list, shuffle it, pick the first option). If you want something priority-based you could rework what happens in the bcontrolstate_ACTIONSELECT_ENEMY segment to use a priority queue instead (ds_priority functions), where you insert things and they're automatically sorted so you can pick the highest priority value - it's pretty much analoguous to how ds_lists work and the only hard part is figuring out how to compute the priorities - you might want to add some new fields to the move database for storing base priority and priority increase per turn to make this easier to control on a case-by-case basis, and maybe rework target selection a bit to account for type matchups (i.e., add every possible move-target combination to the list but multiply the regular priority with the type multiplier for that target)
(Also you might want to still add a little bit of randomness to the priorities, so the AI doesn't become completely predictable and the player can exploit it)
Glad I could help! I've done my best to make everything as simple and easy-to-understand as possible, but between the big sweeping GML changes since I first created this project butchering some of the organizational aspects, and an RPG being pretty dang complex to begin with, things can get a bit overwhelming.
(Btw, if you haven't already, you might want to check out the old comments as well, I've answered a lot of programming questions for misc common features over the years and if you have a future question it might've already been answered in there)
Oh yeah, might need to have a look into that. I know even the released version of Pokémon Colosseum had a bug which let you duplicate items if you used an item as the first party member and then reordered items on the second party member's turn (so you could get infinite Master Balls that way) so it seems this kind of stuff is tricky to get right.
This sounds like it should work as well, it doesn't matter if you enter an invalid state as long as you're forced to revert to a valid state before you can close the menu. Just be careful about implementing autosaving, new ways to cancel the menus (etc) so the player can't end up stuck with too few monsters that way.
I just realized, doing it this way still will cause some issues since grabbing a party monster will move any monsters after it in the party lineup forwards one slot, and the undo overwrites the original slot (so grabbing the first monster when you have only 2 in the party will move the 2nd monster to the 1st slot, then it's overwritten by the undo).
It's probably safer to move the party-amount check to just after line 18 (inside the second "if(menuvalue_x < columns_party){" block), and have the check be something more like this:
var partymons = 0; for(c = 0; c < PARTYSIZE_ACTIVE; c++){ if(amp_read_var(AMP_FIRST_ACTIVE + c,amp_MONID) != NONE){ partymons++; } } if(partymons <= 2){ //Error message code goes here (but without the undo, since we don't always pick up the monster now) } else{ //Regular "pick monster up" code goes here }
So basically, disallow even picking up the monster if you don't have at least 2 after the operation.
You'll need to change the mev_terminal_grab script a bit. This line
if(amp_read_var(AMP_FIRST_ACTIVE,amp_MONID) == NONE){
should be changed to
if(amp_read_var(AMP_FIRST_ACTIVE,amp_MONID) == NONE || amp_read_var(AMP_FIRST_ACTIVE + 1,amp_MONID) == NONE){
and now emptying either of the first two slots in the party (by going below 2 party members) should cancel the operation.
(Also, you probably want to update the message that is printed when this happens to read "2 monsters" instead of "1 monster", a little further down in the script)
The code you posted seems to make sense. The interesting thing here is how you compute sheildcheck, and you didn't include that bit of code...
Also, just to get the obvious out of the way: obj_damageapply still does HP damage and not shield damage, right? (So you didn't accidentally change that one as well when you changed all the variables)
(Also note that your two sheildcheck checks are >0 and <1 so both of them will occur for values between zero and one! Is this intentional?)
trg here is the target monster object and we've already checked that it's alive, so you really only need to check if amp_read_var(trg.amp_id,amp_SHIELDHP) is greater than zero: if so, deal shield damage, else deal regular damage.
All of the collision in this engine is based on simple shapes (which are generated in-engine): axis-parallel cylinders, axis-parallel cubes, and axis-parallel blocks whose top and bottom are bilinear interpolated planes ("slopes" for short). There's no support to load 3D models externally, all geometry in the GMS1 version is generated in-engine using path assets to describe shapes. (Very inefficient, but this was before I learned Blender and I desperately wanted to avoid 3D modelling if I could)
The GMS2 version also adds a global terrain which is generated using an image heightmap (the idea is to cut down on the number of base shapes needed to represent the level geometry, since generating them is so slow) and a more exotic "path" terrain type which is basically a sequence of connected quads with arbitrary positioning - perfect for roads and paths (if they're not very tall and closely aligned to the ground) and curved walls (when they are tall).
Collision checking is the biggest performance hog so ideally you should use cubes and the like as hitboxes whenever possible - even with the stuff done to make this more efficient (using a precomputed hashmap for static terrain) the sheer number of points that needs to be checked makes it take up a lot of processing power.
gguielt_CURRSPRITE element type (added with ggui_element_sprite_current script) is the icon sprite of the currently hovered item in the menu, this is what you want to use here.
Since the data is filled into the menu using ggui_menu_add_option_text it fills the sprite data with NONE, but you could copy ggui_menu_add_option_text and create a new script ggui_menu_add_option_text_and_sprite that also fills the sprite/image data (the two arguments to ggui_fill_meta that are set to NONE within ggui_menu_add_option_text) using two new arguments at the end, and then use that instead in mev_battle_attack. (There's already the _icontext and _gridimage helper scripts that fills the sprite data, but they also set up the elements so that the image is used directly there in the body of the menu, which isn't what you want for this - we want this new script that fills the icon data but doesn't use it yet)
The file labelled Source code (Legacy) (GMS 1.4) is what you're looking for, does that not work for you? Which version are you on?
(Also interested in how it doesn't work, more exactly - if it seems to freeze when you load up a level, it's worth pointing out generating all the geometry in the test level takes a while. If there's some error message you need to tell me what it is and I'll see if I can figure out how to fix it)
Pretty fun concept, I feel like there was a lot of trial-and-error involved (rather than strategically thinking) though.
There's some room left to polish things up, there's some small UX gripes like "start wave" button being darker than every other button (made me think I needed to do something more before it became active even though it always was, for instance) and the way the music doesn't loop properly is a bit grating.
If you bought it on Steam you should be able to download the standalone version from your Yoyo account? I have some faint memory every Yoyo license comes with a matching Steam key and vice versa.
(You should probably contact Yoyo's support about this and not me, I don't work for Yoyogames so I don't have the latest information)
It's still breaking both the EULA and intellectual property laws... Yoyo are backed by the guys that owns OpenAI now so you probably don't wanna pick a legal battle with them if you can avoid it.
You might be interested in the open source alternative LateralGM / Enigma though? (I think it's still around). It's basically a recreation of GM8 and Studio 1.x in Java. I remember it was a bit of a mess to set up since it's two separate projects (for the IDE and compiler) and you need to jump through a few hoops to glue them together, but it's less legal risk and probably less virus/spyware risk as well.
I think you're going about this the wrong way - if you want a visual effect on a per-monster basis it might be easier to have obj_battlemonster handle it? It's already aware of which AMP data block it owns so it's a bit easier than going through the control (who manages a ton of monsters at once) Plus, with your current approach you'll create a new shield effect object every turn - if you don't remove them afterwards you'll end up with dozens or hundreds of them in every battle...
In the draw event, add some new code like this:
if(state == bmonsterstate_NORMAL){ //Monster is guaranteed to be alive and valid in this state if(global.active_monster_party[amp_id,amp_SHIELDHP] > 0){ draw_sprite_ext(spr_shield,0,x+dx,y+dy,image_xscale*drawscale,image_yscale,image_angle,image_blend,image_alpha) } }
There's ways to refine it, like adding effects whenever the shield status changes (broken / regenerated) by keeping track of the status the previous step and doing stuff when it's different from the current step. But the important part for now is making it draw properly.
Either way it's still caused by there being NONE monster IDs in the list of enemies to fight. Weird.
I had a little dig in the codebase and I think I have another idea for what could cause this. The code that checks for valid monsters to send out only checks if HP > 0. If the matching AMP ID's amp_MONID is NONE then we have an invalid monster ID and reading data from that will cause issues.
Do you set up / clear data for totems properly (similar to how the engine handles enemy data in random encounters)? It feels like there might be something going on with partially initialized data that causes this.
One idea might be to extend battle_get_valid_reinforcements a bit so that line 6 checks this condition instead:
if(global.active_monster_party[mon,amp_HP] > 0 && global.active_monster_party[mon,amp_MONID] != NONE){
It's not fixing the core issue that causes the invalid data but it at least stops trying to spawn a nonexistant monster. Maybe it'll be enough, since most other AMP manipulation scripts checks if the monster ID is NONE to check if a slot is valid.
The thing that's happening here is that you try to read the data of a monster that has a NONE AMP reference. It's specifically being done in the block that creates the enemy battlemonster objects.
From what I can see, the issue happens when you start a horde battle without generating enough monsters to fill all enemy horde slots. (This is why it works in 1v1 battles - you always generate 1 enemy monster so creating 1 battlemonster will avoid reading outside the available data)
Should be simple to solve, at least. Wrap this block
in a copy of this condition
and it shouldn't try to read the unset data anymore. (Note how the inner block also changes the monster state from "DEAD" to "NORMAL" when doing this, which will also lead to issues down the line even if you ignore the data reading bit - this is what causes the second crash)