| Mar 4, 2026, 12:57 PM | |
|
Custom objects in MLLE, how to design
This is an idea Faw and I were chatting about some years back, and I never got around to implementing it in MLLE. Before I do any coding, though, it'd be good to make sure this is a workable design, so please sing out if you see any potential problems.
Currently MLLE has a "Configure Weapons" window hidden under the "JJ2+ Properties > Weapons" dropdown menu. If you change any weapons in the level (e.g. to one of these), MLLE takes the following steps (also described here) to make that happen:
Code:
Name = Duo Bouncers ImageFilename = duo bouncers.gif LibraryFilename = DuoBouncers.asc Initialization = DuoBouncers::Weapon() Options = Reverse behaviors:bool | Bounce through walls:bool:true The weapon's constructor passes a function (well, a delegate) DetermineReversedness to WeaponInterface::WeaponInterface's apply argument, which looks like this, where parameter corresponds to those two bytes in Data5: Code:
bool DetermineReversedness(uint, se::WeaponHook@, jjSTREAM@ parameter) {
if (parameter !is null && parameter.getSize() >= 2) {
parameter.pop(ReverseBehaviors);
parameter.pop(BounceThroughWalls);
}
return true;
}
For custom objects, instead of weapons, I think a similar system could work, but with a much simpler coding interface, because a) objects are far more variable than weapons and b) objects tend to only need a single jjObjectPresets entry, whereas a weapon needs to configure the bullet, the powered-up bullet, the pickup, the powerup monitor, and the +15 crate. For an interface, I think this should suffice: Code:
shared interface MLLEObject { bool Apply(uint8 eventID, jjSTREAM@ arguments = null); }
The library code would look something like this: Code:
namespace SlowFeet {
array
Code:
Name = Slow Feet LibraryFilename = SlowFeet.asc Initialization = SlowFeet::Pickup() Options = Ticks:uint:70 Hooks = onPlayer:SlowFeet::onPlayer Event = Slow Feet|+|Goodies|Slow|Feet Code:
#include "MLLE-Include-1.9o.asc" #include "SlowFeet.asc" const bool MLLESetupSuccessful = MLLE::Setup(array On the MLLE side of things, there'd be some window for assigning custom objects to event IDs, which would have room for defining object parameters, here the "Ticks" option. Then the "Event" line in the .ini would be read by MLLE and used in place of that event ID's normal event definition, so for example if you wanted to use event 100 for Slow Feet pickups, MLLE would use 100=Slow Feet|+|Goodies|Slow|Feet instead of 100=Tuf Turtle|-|Enemy|Tuf|Turt like normal. Should that window have limitations? Should events 1-32 not be allowed, or just show a warning if you try? What about when the script (or an included library) already overwrites the ini event for a given event ID? Like ///@Event 164=Beams|-|Trigger|Beams? Should you be allowed to try to overwrite the same event ID in both ways? I can think of objects that are more complicated... for example, the poison/antidote system from CloneV would need two jjObjectPresets entries, not just one. But that could be accomplished using the jjSTREAM argument to pass a second uint8. Other times you might want to have multiple instances of the same class, for different event IDs, and then they might have to rely more on delegates than the above example code. But that's all stuff that the library writer would need to handle. Another design decision is how to handle multiple objects that rely on the same standard hook function. Remember that if multiple weapons need onPlayer, you still only see MLLE::WeaponHook.processPlayer(play);, and that one method handles all the different weapons. But under the above model, for multiple objects, you'd see something like: Code:
void onPlayer(jjPLAYER@ play) {
SlowFeet::onPlayer(play);
Poison::sometimesInjurePlayer(play);
RecolorableSprings::checkYSpeed(play);
MLLE::WeaponHook.processPlayer(play);
}
Code:
interface MLLEObject { bool Apply(uint8, jjSTREAM@); }
interface MLLEObject_onPlayer { void onPlayer(jjPLAYER@) }
interface MLLEObject_onMain { void onMain() }
Then MLLE-Include-1.9o.asc would do something like this in the Setup function: Code:
uint8 eventID; jjSTREAM arguments; data5.pop(eventID); data5.pop(arguments); MLLEObject@ instance = initializerArray[0]; instance.Apply(eventID, arguments); MLLEObject_onPlayer@ instanceOnPlayer = cast<MLLEObject_onPlayer@>(instance); if (instanceOnPlayer !is null) someArrayOfOnPlayerFunctions.insertLast(jjVOIDFUNCPLAYER(instanceOnPlayer.onPlayer)); Code:
void ProcessPlayer(jjPLAYER@ player) {
for (uint i = 0; i < SomeArrayOfOnPlayerFunctions.length; ++i)
SomeArrayOfOnPlayerFunctions[i](player);
}
Code:
void onPlayer(jjPLAYER@ play) {
MLLE::ProcessPlayer(play);
}
Code:
namespace SlowFeet {
array
On a related note, one idea Faw suggested was not using .asc files for custom objects at all, but embedding all their code in the j2as, so the scriptwriter could easily make any level-specific changes they wanted. But again, that introduces a lot of room for the scriptwriter to make mistakes... (I know I could use the issues tracker, but the JCF should get more eyeballs.) Last edited by Violet CLM; Mar 4, 2026 at 01:18 PM. |
| Mar 19, 2026, 11:57 AM | |
|
Hey! It's been a couple weeks, still looking for opinions on these questions
|
| Mar 21, 2026, 03:11 AM | |||||
|
I don't feel 100% competent in answering this, but here's some opinions:
Quote:
Quote:
Quote:
Quote:
__________________
Mystic Legends http://www.mysticlegends.org/ The Price of Admission - Hoarfrost Hollow - Sacrosanct - other - stuff |
|||||
| Mar 21, 2026, 05:13 AM | |
|
One thing I'd like to see - without knowing how doable this is, mind you - would be support for an additional list layer for the event window and its related UI.
The episode I'm working on is very large in scope with many new objects added, and in the end I have every intention to not lock out a level from using the original behavior of an object (etc.) next to the additional one that uses its ID. Example: see how much food I'm adding in the demo levels. You might think no one in their right mind would use more than 255 different food items in a single level, and yes, that's definitely reasonable. But the logical, clean-looking and conveniently usable end goal would be to have the same object asc for all the episode levels, similarly to BL18 or HH24. Even at this very moment there's 92 already, that's over 1/3 of all the event IDs available. Imagine if MLLE supported branching out events further so that the selection list could go, for example...
(I sure hope this displays in a readable manner...) You might suggest that there's already a solution in that I can use event parameters to introduce an additional layer of selection, but here's the thing: it's not quite as readable in MLLE's level view. The squares are limited to the event name, and the parameter is only a small piece of text on the bottom bar - and that's a generic number for anything custom-made. It'd be far more readable if the event display could read what it should say from a label one layer deeper than the event ID name. And an innate support for keeping the original object behavior there as an option from the built-in custom object editor would surely be more inviting for script newbies. Even having done this for a good while now, the logistics of deciding which objects can I overwrite since I won't be using them can be a pain in the butt.
__________________
my stuff |
| Mar 21, 2026, 07:57 PM | |
|
Something that stands out to me is the scope of this feature. It looks like the problem it's addressing is something I've been pondering before -- for the longest time we've been facing an issue where people would like external code to work automagically, without requiring users to understand AngelScript at all, not to mention how to incorporate a particular, likely undocumented library into their script. Every time the main issue is the same: JJ2+ does not provide tools to register multiple hooks of the same type from a single source. I've been thinking about solving this by allowing scripts to run new script modules with a #pragma, or adding built-in hook collections, or, reluctantly, allowing hook registration through metadata. I consider code generation something of a last resort, but of course it also works. The parametrization through a UI is, naturally, invaluable either way, so some support from MLLE would've been required regardless.
So my perception is that, at its core, this feature is about configuring and running "modules" -- independent external snippets of code that can hook into any aspect of the game. This raises a question: why (attempt to) scope it to objects? My latest single player level, Transantarctic Mountains, features multiple scripted events. Besides custom objects, I also used modified Hurt events, as well as the following curiosity: Code:
///@Event 254=Delet This |-|Modifier |NOT |v
void deleteUnwantedEvents() {
int width = jjLayerWidth[4];
int height = jjLayerHeight[4] - 1;
for (int i = 0; i < height; i++) {
for (int j = 0; j < width; j++) {
int event = jjEventGet(j, i);
if (event == 254)
jjParameterSet(j, i + 1, -12, 32, 0);
}
}
}
void onLevelLoad() {
deleteUnwantedEvents();
}
As far as I can tell, the proposed feature would support this event perfectly well as it stands, despite the fact that it is most certainly not an object. It would also support the custom Hurt events and various other custom events falling firmly into the categories of "areas" or "modifiers" that don't actually produce any objects. This alone doesn't really provoke design changes -- it's a good thing that the design allows for this -- but it does make the naming seem silly. People will most likely use the feature for this purpose if they realize they can. It doesn't stop there though. It seems entirely reasonable that someone may want to import a script that defines no events at all, but rather modifies the game more globally. Perhaps to give the level toroidal topology like in Hyperspace, or a complex weather condition like in Under The Weather, or even to do something super simple that doesn't take that much code but is just preferable for them to do with a UI. Now the design of the feature remains technically suitable but at the same time begins clearly getting in the way. I don't actually need to overwrite an event for this, but I'm forced to, and presumably MLLE helpfully prevents me from recycling event IDs, so I'm limited in terms of how many of these modules I can actually import, and my event list is filling up with dummy entries. These drawbacks seem like they wouldn't be enough to actually discourage this kind of use, merely make it a little annoying. What this suggests to me is that this system is much too powerful and potentially useful to be limited to objects by design. You mention that scripts may want to use the API to define multiple objects (really events) at once, and I claim that they may also want to define none, so I wonder if it would be beneficial to do away with this default of exactly one, or even the assumption that defining events is the objective of the system, rather than just one of things it can do.
__________________
I am an official JJ2+ programmer and this has been an official JJ2+ statement. |||||||||||||||||||||||||||||||||||||||||||||||||| |
![]() |
«
Previous Thread
|
Next Thread
»
| Thread Tools | |
|
|
All times are GMT -8. The time now is 03:09 AM.
Jazz2Online © 1999-INFINITY (Site Credits). Jazz Jackrabbit, Jazz Jackrabbit 2, Jazz Jackrabbit Advance and all related trademarks and media are ™ and © Epic Games. Lori Jackrabbit is © Dean Dodrill. J2O development powered by Loops of Fury and Chemical Beats. Powered by vBulletin® Copyright ©2000 - 2026, Jelsoft Enterprises Ltd.
Original site design by Ovi Demetrian. DrJones is the puppet master. Eat your lima beans, Johnny.






