I found two more glitches concerning builders, that might require slight changes in the game physics. Here is the first:
Suggestion:
Disallow assigning builders to lemmings, if their first brick would not add any terrain.
Reason:
1) Repeated assignment of builders allows to move lemmings diagonally through solid walls. Try the attached level or watch the replay.
2) This rule already applies to platformers, so this change would make the game mechanics more consistent.
Moreover I don't think that there are many existing levels/replays where lemmings completely encased in terrain are turned into builders, so this change should not have a huge impact on existing levels/replays.
QuoteDisallow assigning builders to lemmings, if their first brick would not add any terrain.
Sounds reasonable and viable (note - my first thought was "wait, that will break every level that involves building against a slope to turn around!" - but in fact, this action does still add one pixel of terrain, so it wouldn't be affected).
Tried implementing this. One situation that your suggestion does not solve:
I see... What are the precise terrain checks for platformers? Perhaps one can use something similar for builders? I noticed that in your example level, one cannot assign platformers to the trapped lemming.
The platformer is also prevented from platforming if his first brick would cause him to turn around, even if it would place terrain. For obvious reasons, doing this with the builder would break a lot of levels. Removing such a thing with the platformer is perhaps more viable; it would improve consistency with far less breakage, but still wouldn't really fix the issue at hand.
It's mostly LemCanPlatform and AssignPlatformer that are relevant here, but I've pasted HandlePlatforming too just in case it helps.
TLemmingGame.HandlePlatforming
function TLemmingGame.HandlePlatforming(L: TLemming): Boolean;
begin
Result := False;
with L do
begin
// sound
if (LemFrame = 10) and (LemNumberOfBricksLeft <= 3) then
CueSoundEffect(SFX_BUILDER_WARNING);
// lay brick
if (LemFrame = 9) then
begin
LemCouldPlatform := LemCanPlatform(L);
if not CheckGimmick(GIM_UNALTERABLE) then
LayBrick(L, 1);
if CheckGimmick(GIM_HARDWORK) then
LemNumberOfBricksLeft := 11;
if (CheckGimmick(GIM_LAZY)) and (LemNumberOfBricksLeft > 4) then
LemNumberOfBricksLeft := 4;
Exit;
end
else if (LemFrame = 15) and not (LemCouldPlatform) then
begin
Transition(L, baWalking, TRUE);
if (CheckGimmick(GIM_UNALTERABLE)) then Transition(L, baShrugging);
if CheckGimmick(GIM_BACKWARDS) and HasPixelAt(LemX, LemY-1) and not HasPixelAt(LemX + LemDx, LemY - 1) then
Inc(LemX, LemDx);
CheckForLevelTopBoundary(L);
Result := True;
Exit;
end
else if (LemFrame = 0) or (LemFrame = 15) then
begin
Inc(LemX, LemDx);
if (((HasPixelAt_ClipY(LemX+LemDx, LemY - 1, -1) = TRUE)
or (HasPixelAt_ClipY(LemX+LemDx, LemY - 2, -1) = TRUE))
and ((LemNumberOfBricksLeft > 1) or (LemFrame = 15))) then
begin
Transition(L, baWalking, TRUE); // turn around as well
if (CheckGimmick(GIM_UNALTERABLE)) then Transition(L, baShrugging);
if CheckGimmick(GIM_BACKWARDS) and HasPixelAt(LemX, LemY-1) and not HasPixelAt(LemX + LemDx, LemY - 1) then
Inc(LemX, LemDx);
CheckForLevelTopBoundary(L);
Result := True;
Exit;
end;
if LemFrame = 0 then
begin
Inc(LemX, LemDx);
if (((HasPixelAt_ClipY(LemX+LemDx, LemY - 1, -1) = TRUE)
or (HasPixelAt_ClipY(LemX+LemDx, LemY - 2, -1) = TRUE))) then
begin
if (LemNumberOfBricksLeft > 1) then
begin
Transition(L, baWalking, TRUE); // turn around as well
if (CheckGimmick(GIM_UNALTERABLE)) then Transition(L, baShrugging);
if CheckGimmick(GIM_BACKWARDS) and HasPixelAt(LemX, LemY-1) and not HasPixelAt(LemX + LemDx, LemY - 1) then
Inc(LemX, LemDx);
CheckForLevelTopBoundary(L);
Result := True;
Exit;
end else if HasPixelAt_ClipY(LemX, LemY - 1, -1) then
Dec(LemX, LemDx);
end;
Dec(LemNumberOfBricksLeft);
if (LemNumberOfBricksLeft = 0) then
begin
Transition(L, baShrugging);
CheckForLevelTopBoundary(L);
Result := True;
Exit;
end;
end;
Result := True;
Exit;
end
else begin
Result := True;
Exit;
end;
end; // with L
end;
TLemmingGame.LemCanPlatform
function TLemmingGame.LemCanPlatform(L: TLemming): Boolean;
var
x, x2: Integer;
c2: Boolean;
begin
with L do
begin
x := LemX;
if LemDX < 0 then Dec(x, 5);
Result := false;
c2 := true;
for x2 := x to x+5 do
if not HasPixelAt(x2, LemY) then
begin
if x2 < 3 then c2 := false;
Result := true;
end;
if LemDX < 0 then Inc(x, 2);
if c2 and Result then
for x2 := x+1 to x+2 do
if HasPixelAt(x2, LemY-1) then
Result := false;
end;
end;
TLemmingGame.AssignPlatformer
function TLemmingGame.AssignPlatformer(Lemming1, Lemming2: TLemming): Boolean;
const
ActionSet = [baWalking, baShrugging, baBuilding, baStacking, baBashing, baMining, baDigging];
begin
Result := False;
if not CheckSkillAvailable(baPlatforming) then
Exit;
if (Lemming1.LemAction in ActionSet) and LemCanPlatform(Lemming1) then
begin
if (fCheckWhichLemmingOnly) then
WhichLemming := Lemming1
else
begin
Transition(Lemming1, baPlatforming);
UpdateSkillCount(baPlatforming);
Result := True;
RecordSkillAssignment(Lemming1, baPlatforming);
if not fFreezeSkillCount then
begin
OnAssignSkill(Lemming1, baPlatforming);
GameResultRec.gScore := GameResultRec.gScore - SCS_PLATFORMER;
end;
end;
end
else if (Lemming2 <> nil) and (Lemming2.LemAction in ActionSet) and LemCanPlatform(Lemming2) then
begin
if (fCheckWhichLemmingOnly) then
WhichLemming := Lemming2
else
begin
Transition(Lemming2, baPlatforming);
UpdateSkillCount(baPlatforming);
Result := True;
RecordSkillAssignment(Lemming2, baPlatforming);
if not fFreezeSkillCount then
begin
OnAssignSkill(Lemming2, baPlatforming);
GameResultRec.gScore := GameResultRec.gScore - SCS_PLATFORMER;
end;
end;
end;
end;
One possible solution is simply to disallow both if there's a pixel at (LemX, LemY-1). Generally, in any state where this would be true, it would already be impossible to assign these skills anyway (as the lemming would generally be either climbing or jumping at such a time). The only exceptions are (a) the situation demonstrated in Glitchy Spikes, and (b) if a constructive skill had, in the frame immediately prior, placed a pixel at that location - most likely to happen with a stoner, but could theoretically happen with any of them.
Quote from: namida on February 22, 2016, 06:28:11 PM
One possible solution is simply to disallow both if there's a pixel at (LemX, LemY-1).
Sounds like a good idea.
PS: Your LemCanPlatform causes some irregular behavior as well. Will post a new topic with example map.
Hmm, it's my understanding that this wouldn't work in vanilla Lemmix (ie. DOS Lemmings mechanics). The builder does advance horizontally one pixel at the frame it turns around and reverts to walker. But, the walker then will always move horizontally one pixel (facing opposite direction now) on next frame before turning around again. So there is no net gain horizontally (vertically of course you do gain one pixel per builder).
If I have to guess, NeoLemmix must have changed the second part ("the walker then will always advance horizontally one pixel...") if it enables horizontal movement through solid wall? Or maybe if you assign a different skill to the walker that enables the second turnaround without any horizontal movement.
[edit: I should add one more clarification. If your worker is a climber, then you do get a horizontal gain, because at the second part with the walker, it will trigger the climber to do its "climb and fall" thing which gains you 2 pixels horizontally. This variation does work in vanilla Lemmix, but is arguably more a side effect of the climber's odd behavior.]