Show Posts

This section allows you to view all posts made by this member. Note that you can only see posts made in areas you currently have access to.


Messages - Charles

Pages: [1] 2 3 4
1
Lemmini / Re: Infinite skill deducts to 2^31 − 2 during replay
« on: December 15, 2022, 03:48:04 AM »
is the Max-Value implied to be that huge number from my post above or infinity? Or was the value for it declared somewhere in the code and I just didn't see it? Similarly, am I correct to say that -- and ++ mean -1 and +1, respectively?

Yup, you've got the gist. Integer.MAX_VALUE is exactly as Simon mentioned earlier... a named literal that equals the highest value that can be represented by a 32-bit Integer. i.e. 2^32-1.  That name is built-in to Java.  SuperLemminiToo (and SuperLemmini, and presumably Lemmini before it) it coded to interpret a MAX_VALUE as meaning Infinity, and to display the UI as such.

Note, that MAX_VALUE does not inherently mean Infinity. We're just using it that way. We could just as easily check NumClimbers != (2^32-1) or NumClimbers != 2147483647. Using integer.MAX_VALUE is just more convenient, and makes for easier to read code.  There are data types that do allow you to represent actual infinity.  Like Double.NEGATIVE_INFINITY, or Double.POSITIVE_INFINITY, or even Double.NaN (Not a Number, like divide by 0).  But thsoe are only for Float types, and don't make sense for discrete values like the number of skills left.

And yup, -- is -1, and ++ is +1.  Not sure if Java allows this, but in C, x++ is different from ++x. x++ uses the x variable then increments it, whereas ++x increments it first then uses it.

Anyway, that's far enough of a tangent.  i pushed a new release to fix the bug. v1.51. It's on the stickied topic for SuperLemminiToo.

2
Lemmini / Re: SuperLemminiToo v1.50
« on: December 15, 2022, 03:35:06 AM »
Bugfix release.  Here's the update to fix the infinite skills decreasing during replays, discovered by kaywhyn.

3
Lemmini / Re: Infinite skill deducts to 2^31 − 2 during replay
« on: December 14, 2022, 12:19:30 AM »
if you enter replay mode by, say, restarting the level or nuking and then clicking on restart on the post-results screen, once you use the skill(s) for which you have an infinite amount of, it will show that you have 2,147,483,646 (two billion one hundred forty seven million four hundred eighty three thousand six hundred forty six) of that skill left

That's a great bug find!

I can see exactly what's causing it, too. The code for decrementing the skills is different for during a replay and for when you manually click on a lemming (for no real good reason I can see...)

Manually clicking has a check to make sure the value is not already the highest integer value, and won't decrease if that's the case... the replay decrement code is missing that validation.

You can see the exact code in the GameController.java file:
Good code: https://github.com/Blazingstix/SuperLemminiToo/blob/main/src/lemmini/game/GameController.java#L1371
Bad code: https://github.com/Blazingstix/SuperLemminiToo/blob/main/src/lemmini/game/GameController.java#L1084

I'll try and update that code and publish a new version of SuperLemminiToo before Christmas.

4
Lemmini / Re: SuperLemminiToo v1.50
« on: March 22, 2022, 06:43:57 PM »
Bugfix release.  Here's the update to address the bug WillLem discovered, related to saving stats with Unlock All Levels enabled.
(see https://www.lemmingsforums.net/index.php?topic=5947.0)

I also added a setting called Classic Ticker that replaces the scrolling text on the title screen with the same text on a scrolling yellow ticker tape graphic, reminiscent of the original Amiga title screen.

5
I'm finding some of the same problems as you with Eclipse, for what it's worth.

It feels really inconsistent as to when it's pulling from src of pulling from bin.  I'm a bit rusty on it, so I don't remember all the nuances of it, I just remember I had a heck of a time when I was constantly updating the root.lzp for a bunch of the updates.

The code as I modified it should show records for the 1st level (always), and any levels flagged as "unlocked" by the large bit integer, and (assuming unlock all levels is turned on) any levels that have a group#_level#_completed=true value in the player.ini.

I'll try and package the updates I did into a new release tonight... then you can compare if your code updates work the same as mine, and we can see if the bug's properly fixed or not.

6
All existing records in player.ini, however, are not shown (but nor are they wiped from player.ini, as I accidentally did yesterday when trying a few things out!)

Are you sure all your records are intact?

I've reviewed the records displaying code, and the only thing there is if the level's not complete it won't show any records.  Which is not the same as if the level's available.  The level will always be available if the unlock levels flag it set.

Maybe it's possible the the earlier bug caused only some of your records to be erased?  Look for a level that's not showing records you expect, then check the player.ini file looking for "group#_level#_completed=true".  If it's false (or doesn't exist) then any other records for that level won't be displayed.  Oh, and because it doesn't appear to save the file in any particular order, I recommend opening it up in Notepad++ (or similar) and sorting the lines. In Notepad++ you can do it under Edit-->Line Operations-->Sort Lines...

7
There’s no distinction between “old” data and “new” data in player.ini.

Maybe it’s time to look at the record display code. I think it’s loading all the records correctly this time. At least I can’t think of any gaps in our loading code.

8
Sorry for the double-post, but I went ahead and wrote up option 3 afterall.

So, this part goes in your Props.java:
Code: [Select]
    /**
     * Returns the highest level number present for the supplied Group, in the records.
     * @param groupNum The group number from the player INI file to check levels against
     * @return -1 if no matches found, or the highest level number otherwise (starting at 0).
     */
    public int getHighestLevel(int groupNum) {
    Set<Object> keys = hash.keySet();
    int max = -1;
    for(Object k:keys) {
    String key = k.toString();
    String match = "group" + groupNum + "_level";
    if(key.startsWith(match)) {
    int idx2 = key.indexOf("_", match.length()-1);
    String tmpNum = key.substring(match.length(), idx2);
    try {
    int num = Integer.parseInt(tmpNum);
    if (num > max) {
    max = num;
    }
    } catch (NumberFormatException e) {
    //just catching errors because we have no way of trusting the data we're inputting.
    //but we don't need to actually do anything with the error... just to prevent it from bringing down the whole system.
    }
    }
    }
    return max;
    }

And in Player.java (in the constructor), replace this:
Code: [Select]
                for (int j = 0; j < unlockedLevels.bitLength(); j++) {
                    if (j == 0 || unlockedLevels.testBit(j)) {
                        boolean completed = props.getBoolean("group" + idx + "_level" + j + "_completed", false);
                        if (completed) {
                            int lemmingsSaved = props.getInt("group" + idx + "_level" + j + "_lemmingsSaved", -1);
                            int skillsUsed = props.getInt("group" + idx + "_level" + j + "_skillsUsed", -1);
                            int timeElapsed = props.getInt("group" + idx + "_level" + j + "_timeElapsed", -1);
                            int score = props.getInt("group" + idx + "_level" + j + "_score", -1);
                            levelRecords.put(j, new LevelRecord(completed, lemmingsSaved, skillsUsed, timeElapsed, score));
                        } else {
                            levelRecords.put(j, LevelRecord.BLANK_LEVEL_RECORD);
                        }
                    }
                }

with this:
Code: [Select]
                int maxLevel = props.getHighestLevel(idx) + 1;
                maxLevel = Math.max(maxLevel, unlockedLevels.bitLength());
                for (int j = 0; j < maxLevel; j++) {
                    //if (j == 0 || unlockedLevels.testBit(j)) {
                        boolean completed = props.getBoolean("group" + idx + "_level" + j + "_completed", false);
                        if (completed) {
                            int lemmingsSaved = props.getInt("group" + idx + "_level" + j + "_lemmingsSaved", -1);
                            int skillsUsed = props.getInt("group" + idx + "_level" + j + "_skillsUsed", -1);
                            int timeElapsed = props.getInt("group" + idx + "_level" + j + "_timeElapsed", -1);
                            int score = props.getInt("group" + idx + "_level" + j + "_score", -1);
                            levelRecords.put(j, new LevelRecord(completed, lemmingsSaved, skillsUsed, timeElapsed, score));
                        } else {
                            levelRecords.put(j, LevelRecord.BLANK_LEVEL_RECORD);
                        }
                    //}
                }

It's just the top three lines that are changed.  We'll get the highest level num for this group in the ini level records, and just in case if the unlocked bits is higher, we'll take that instead.

9
is it worth adding some code for handling of special characters in group names, or is this a bigger task than I'm imagining it might be...?
I'm playing with some code, which I think is a "good enough" solution. It's tricky, because the "proper" way to fix it would be to fix the code in Props.java, right at the source in getArray (actually there's a bunch of getArray functions, so you'd want to fix them all). The getArray functions are little helper functions that retrieve a value from an ini file, and then treat it like a CSV array (comma separated value). It splits the string into an array of smaller strings wherever it finds a comma, then trims any spaces at the beginning and end of each one. (That trimming is important to note for later)
So a more thorough fix would be to create a setArray function that accepts an array as a parameter and does proper character escaping when it encounters a comma (i.e. replace , with \, or something), then fix up the getArray to recognize those character escapings when reading it back in.

I didn't do that. I took a lazier approach:
Code: [Select]
            System.out.println("    loading level group " + idx);
            // first string is the level group key identifier
                // second string is a BigInteger used as bitfield to store available levels
                String[] s = props.getArray("group" + idx, null);
                if (s == null || s.length < 2 || s[0] == null) {
                    System.out.println("    no valid group data found... breaking...");
                break;
                }
                String s1 = s[s.length-1]; // get the last
                String groupName = "";
                // due to a bug in the ini property "Array" saving, commas are not escaped, and so they're treated as you would any column separator
                // because the getArray function above trims out any spaces, knowledge if there was or was not a space after a comma is lost
                // so let's just assume there's always a space after it, because that's better grammar.
                for (int a = 0; a < s.length-1; a++) {
                groupName += s[a];
                if (a < s.length-2 ) {
                groupName +=", "; //we're assuming there's always a space after any comma
                }
                }
                System.out.println("    group name identified: " + groupName);
So previously the code would getArray to get the CSV from the Player.ini file. It would only proceed if there were exactly 2 values: 1) the group name, and 2) the unlockedLevels. More or less than 2 values, and it would skip onto the next group.  So now, I read in all values, and only skip if there are less than 2. Instead of taking the 2nd value as the unlockedBits, I take the last value as the bits.
For the group name, I concatenate all the other values together. And I put a ", " between them.

So, the only thing I've lost is that because the getArray trims any spaces, I have no way of knowing if there's supposed to be a space after the comma or not. So I just assume there is, and leave it at that.

I figured that removing the "else = blank record" script altogether might leave it so that records would be displayed for all levels, whether they were unlocked or not. But, it seems that the  "up to a certain point" code is still being naughty.

I then tried removing this entire section of code altogether, and it wiped my records (luckily I had a backup!). Really not sure what to do about this, but willing to learn.
It's not surprising that commenting out that (or a part of it) didn't help. The else put(BLANK_LEVEL_RECORD) part isn't replacing or hiding existing records.

For each level, the game saves "completed (true/false)", "lemmingsSaved (integer)", "skillsUsed (integer)", "timeElapsed (integer)", "score (integer)".  The assumption is that if "completed" is false (or doesn't exist) then logically the other values shouldn't exist either... they're only stats for if the level is completed.  So, if the level's not completed, don't even try to load non-existant stats. Just create a blank record.  I think that assumption still holds water.

Could it be made so that any available stats are displayed, regardless of a level's locked/unlocked status?
Yes, but not without some modifications.  That's exactly what the code we commented out in the first place did.  if ( j == 0 || unlockedLevels.testBit(j) )  That's saying, try and load the records if it's the 1st level in the group, OR the level is unlocked.  Removing that just loads all the stats up to the last level the ini files knows about. Unfortunately that is the problem. The INI file doesn't know how many levels total there are... it can't tell the code where to stop, and at this point, the INI file is all we're asking.

So, I see 2 possible solutions around that: 1) add the total level count to the ini file, or 2) query whatever does know how many levels there are.

Stats for listed levels (whether locked or unlocked) should always be displayed...
That's also a valid 3rd option.  But I don't know that I'd consider any option trivial.

For option 3, the difficulty is that currently the code specifies what key to read by name, one after the other. i.e. group0_level0_completed, then group0_level1_completed, ... , group0_level99_completed, ... , group0_level100_completed... etc.  Without knowing how many levels there are, what number should you decide to stop at?  Instead you have to flip that on it's ear, and loop through all the properties that do exist. I don't believe that code exists yet, so you'd have to write it.  Probably best to put it in the Props.java. It might look something like:
Code: [Select]
public int getHighestLevel(int groupNum) {
    //psuedo-code
    for each key in Properties
    if key.startsWith("group" + grounNum + "_level") then
        get the digits up to the next _ and compare with the next key until you find the highest one.

   return highestNum;
}
Hmm... yeah, actually I do like option 3 the best. It retains the most backwards compatibility with SuperLemmini ... and, if there's no levels saved, who cares if it's unlocked or not (as far as record showing goes).

The other options I was thinking of require making changes to how the INI is saved, or I don't even know yet where to read in the total level number otherwise.

10
Fantastic debug work, WillLem!

The Player records wasn't something I'd ever looked at before, so SLToo didn't change any of the code there, but I certainly exacerbated this problem by more easily exposing the UnlockAllLevels stuff.

Reviewing it now, I can confirm that commas in the group names do break the records.  It's a case of Props.java not doing any special handling of commas when writing, and then just reading it in as if it was a standard csv (splitting into columns by the commas).

You've probably seen a line like this before in the player.ini files
Code: [Select]
group13=lemmings, but with lemminas\!-fun, 5That's saving the record details in the form of group<IDX>=<NAME>-<RATING>, <UNLOCKED_BITS>
The <UNLOCKED_BITS> part is also interesting in itself.  It's a binary number representing which levels are unlocked. 1 means the level is unlocked, 0 is locked. i.e from the above, 5 in binary is 0101, so (starting at 0 and reading from right to left) levels 0 and 2 are unlocked, while all the others are locked.

I didn't play around with line 2560 in GamneController.java (my line 2560 didn't seem to line up with yours anyway), but I did play around with Player.java. I think I found some code that was promising, but I couldn't do much testing.

To help me learn, I was adding a bunch of comments and System.out.println() statements, so my line numbers won't match yours but, look for the part in the constructor that looks like this:
Code: [Select]
                for (int j = 0; j < unlockedLevels.bitLength(); j++) {
                    if (j == 0 || unlockedLevels.testBit(j)) {
                        boolean completed = props.getBoolean("group" + idx + "_level" + j + "_completed", false);
                        if (completed) {
                            int lemmingsSaved = props.getInt("group" + idx + "_level" + j + "_lemmingsSaved", -1);
                            int skillsUsed = props.getInt("group" + idx + "_level" + j + "_skillsUsed", -1);
                            int timeElapsed = props.getInt("group" + idx + "_level" + j + "_timeElapsed", -1);
                            int score = props.getInt("group" + idx + "_level" + j + "_score", -1);
                            levelRecords.put(j, new LevelRecord(completed, lemmingsSaved, skillsUsed, timeElapsed, score));
                        } else {
                            levelRecords.put(j, LevelRecord.BLANK_LEVEL_RECORD);
                        }
                    }
                }

(Just before this code the game has loaded the level group name/rating and the unlocked bits.) So what's going on here is that the game is now loading the individual level stats for that level group/rating. BUT first off, it's only checking up to the highest unlocked level (j < unlockedLevels.bitLength();), and then secondly if that level is flagged as not unlocked, then it's not even bothering to load that level's stats at all.

I haven't looked at the code that decides what levels are locked or unlocked, and probably this whole section will need an overhaul, but a quick fix might be to just comment out the if (j == 0 || unlockedLevels.testBit(j)) bit, so it loads all level stats regardless if the level's flagged as locked or not.

So...
Code: [Select]
                for (int j = 0; j < unlockedLevels.bitLength(); j++) {
                    //if (j == 0 || unlockedLevels.testBit(j)) {
                        boolean completed = props.getBoolean("group" + idx + "_level" + j + "_completed", false);
                        if (completed) {
                            int lemmingsSaved = props.getInt("group" + idx + "_level" + j + "_lemmingsSaved", -1);
                            int skillsUsed = props.getInt("group" + idx + "_level" + j + "_skillsUsed", -1);
                            int timeElapsed = props.getInt("group" + idx + "_level" + j + "_timeElapsed", -1);
                            int score = props.getInt("group" + idx + "_level" + j + "_score", -1);
                            levelRecords.put(j, new LevelRecord(completed, lemmingsSaved, skillsUsed, timeElapsed, score));
                        } else {
                            levelRecords.put(j, LevelRecord.BLANK_LEVEL_RECORD);
                        }
                    //}
                }
The only problem that still might remain is if there are stats for levels higher than the highest level flagged as unlocked.  For example, going back to my example with the 5 above, only levels 0 and 2 are flagged as unlocked, so it still won't load stats for levels 3-30 or higher. And that can't be fixed without a major change... the total number of levels in a level group/rating just isn't saved here at all.

11
Lemmini / Re: SuperLemminiToo v1.46
« on: December 13, 2021, 03:02:49 PM »
Regarding the Timed Bomber option:

Is there any way this feature can be enabled/disabled per-pack (or per level), i.e. from within the levelpack.ini file (or level file), thus overriding the user-selected option?

Yeah, absolutely. That's part of my plans for sometime in the next couple of versions.

I'm going to change the options toggle into a timed bomber override, as a trinary toggle, with on being yes timed bombers, off being no timed bombers, and maybe being use level specified setting.
If the level doesn't specify, it will be no timed bombers (as that seems likely to be the most compatible with how the state of the Lemmings fan communities are today).

I'll also update all the levels included with SuperLemminiToo to specify timed bombers are enabled.

12
Lemmini / Re: SuperLemminiToo v1.46
« on: December 03, 2021, 06:22:52 PM »
Updated release: v1.46
- updated animated icons to 256-color from 16-color.
- moved enhanced iconbar icons up 5 pixels.

13
Lemmini / Re: SuperLemminiToo v1.45
« on: December 03, 2021, 06:13:43 PM »
I've struggled with the icon bar a lot.  I really wanted to stick to the original animated icons as much as possible, which is why I settled on as large a size as I did. The animated icons from Lemmings 95 are 32x40 pixels, so to put that in a button I need to add a 1 pixel border all around, bringing me to 34x42. Then to bring in the skill counters, I needed to add 9 pixels (34x51), then add some padding so they're not touching the edge of their black background, nor the (theoretical) top of the icons. Thus 34x54.

But, if I look at the animated icons themselves, there's only 2 frame of 1 icon that actually maxes out the full 32x40 size... the bomb, when it's exploded.  All the other icons have a fairly large blank space at the top, almost as if they're expecting to accommodate a skill counter already.

It was my trying to fit each icon to the fullest that caused them to be really badly weighted on the buttons.

I've decided to change my stance a bit... There's no reason to keep all the icons on the floor all for the sake of 2 frames of an explosion not being covered by the skill counter. I'm moving all the icons up 5 pixels in code to better center them, but I'm leaving the actual icons as they are -- sorry for making you make so many changes to your icons, WillLem.

I've also taken another look at the colours. I appreciate the efforts you've gone through to recolour the icons away from the outdated 16-colours.  I personally think they look a bit dark. I know they match the original static icons very well, but I'm leaning to something a bit brighter, and that fits more in line with the actual lemming sprites themselves.  So I've also recoloured the animated icons myself, basing them around the lemming sprites... I haven't matched the colours exactly, but I have tried to make them at least resemble the sprite colours, while retaining a certain "pop" with their brightness.  Tastes will vary, but I like the look of them.  I also chose to keep the restart icon Red.  I like the urgency/danger that red denotes.  Restarting the level is a non-reversable process, and isn't for casual clicking.

I'll publish that update shortly -- v1.46.  And after that I don't plan on doing any work on the iconbar for a while -- there are many other interesting areas I'd like to touch up next.

14
Lemmini / Re: SuperLemminiToo v1.45
« on: December 02, 2021, 04:41:58 AM »
new release v1.45
This is a bit of an unplanned release... I have a rough roadmap in my head, and I haven't done enough for what I consider v1.50, so this is a halfway step... v1.45.

I've finalized the filenames for the enhanced iconbar images, to properly support cross-compatibility between SuperLemminiToo and SuperLemmini.  WillLem, you'll need to make some minor adjustments to your updated mods, which is why I wanted to get this release out there as quick as I could, so you and other mod makers don't have a moving target to support.

The big changes:
* I've moved all the new images for the IconBar into the /gfx/iconbar/ folder.  (this includes iconbar_filler.png, and the 3 large_minimap_xxx.png images
* I've also moved and renamed the transparent skill icons, into the same /gfx/iconbar/ and prefaced them with ticon.
So, /gfx/iconbar/ now has: icon_empty_large.png, iconbar_filler.png, 3 large_minimap_xxx.png images, 15 ticon_<skill>.png transparent static images, 15 anim_<skill>.png animated images.

The way it works is like this... SuperLemminiToo will first check if there are any icons at all in the /gfx/iconbar/ folder.  If there isn't, then it assumes the mod does not have support for SuperLemminiToo, and will apply whatever icons it does find, even if they look ugly.  That's pretty much the behaviour v1.40 an earlier did. If there *is* a /gfx/iconbar/ folder, then it will ignore the icons in the gfx/icons folder. 

In that case, it will check for an animated icon in the mod first, then a static transparent icon in the mod, then an animated icon in the root, then a static transparent icon in the root.

So to support both SuperLemminiToo and SuperLemmini, as a mod maker, you should create the icons with background in the /gfx/icons/ folder, and transparent icons in the /gfx/iconbar/ folder (and animated icons too).  You should also use a minimalist philosophy... if your custom icon isn't any different from the default icon, don't include it -- remember the default icon will be applied over-top of your custom icon background now. So if you're only modifying the skill icons and the empty icon, all the others will get updated for free because they'll get auto-composited on top of the new empty icon.

WillLem, I haven't included your newly coloured animated icons in this update, because I just wanted to focus on locking down the filenames.  I also looked at adjusting the size of the icons further, like you'd suggested but ultimately, I'm satisfied with them as they are.  I'm considering these sizes locked in: 32x40 for the standard iconbar, and 34x54 for the enhanced icon bar. (EDIT: NOTE: The animated icons, and transparent icons are still only 32x40, but are composited into the larger background). I did move the labels down to the bottom though, for all of them.  I think I'll play around later with giving them some drop-shadow or something, so the white doesn't get so lost on all the icons, but I do like how it's more consistent across the board.

15
Lemmini / Re: SuperLemminiToo v1.40
« on: November 28, 2021, 05:30:17 PM »
Those look fantastic, WillLem!

Pages: [1] 2 3 4