A Classic Fairytale: Harden all missions against missing campaign variables in team file and assume default values
This assumes the worst case in which the team file is missing all campaign variables except Progress.
This has been successfully tested with all 10 missions and still generates a logical storyline.
By default, the game assumes:
- The cyborg's offer in mission 2 was refused
- The traitor in mission 5 was killed
As a consequence, missions 8 and 10 use the princessScene cut scene.
unit uLandGenTemplateBased;
interface
uses uLandTemplates, uLandOutline;
procedure GenTemplated(var Template: TEdgeTemplate);
procedure DivideEdges(fillPointsCount: LongWord; var pa: TPixAr);
var minDistance, dabDiv: LongInt; // different details size
implementation
uses {$IFDEF IPHONEOS}uTypes, {$ENDIF} uVariables, uConsts, uFloat, uLandUtils, uRandom, SDLh, math;
procedure SetPoints(var Template: TEdgeTemplate; var pa: TPixAr; fps: PPointArray);
var i: LongInt;
begin
with Template do
begin
pa.Count:= BasePointsCount;
for i:= 0 to pred(LongInt(pa.Count)) do
begin
pa.ar[i].x:= BasePoints^[i].x + LongInt(GetRandom(BasePoints^[i].w));
if pa.ar[i].x <> NTPX then
pa.ar[i].x:= pa.ar[i].x + ((LAND_WIDTH - Template.TemplateWidth) div 2);
pa.ar[i].y:= BasePoints^[i].y + LongInt(GetRandom(BasePoints^[i].h)) + LAND_HEIGHT - LongInt(Template.TemplateHeight)
end;
if canMirror then
if getrandom(2) = 0 then
begin
for i:= 0 to pred(BasePointsCount) do
if pa.ar[i].x <> NTPX then
pa.ar[i].x:= LAND_WIDTH - 1 - pa.ar[i].x;
for i:= 0 to pred(FillPointsCount) do
fps^[i].x:= LAND_WIDTH - 1 - fps^[i].x;
end;
(* Experiment in making this option more useful
if ((not isNegative) and (cTemplateFilter = 4)) or
(canFlip and (getrandom(2) = 0)) then
begin
for i:= 0 to pred(BasePointsCount) do
begin
pa.ar[i].y:= LAND_HEIGHT - 1 - pa.ar[i].y + (LAND_HEIGHT - TemplateHeight) * 2;
if pa.ar[i].y > LAND_HEIGHT - 1 then
pa.ar[i].y:= LAND_HEIGHT - 1;
end;
for i:= 0 to pred(FillPointsCount) do
begin
FillPoints^[i].y:= LAND_HEIGHT - 1 - FillPoints^[i].y + (LAND_HEIGHT - TemplateHeight) * 2;
if FillPoints^[i].y > LAND_HEIGHT - 1 then
FillPoints^[i].y:= LAND_HEIGHT - 1;
end;
end;
end
*)
// template recycling. Pull these off the floor a bit
if (not isNegative) and (cTemplateFilter = 4) then
begin
for i:= 0 to pred(BasePointsCount) do
begin
dec(pa.ar[i].y, 100);
if pa.ar[i].y < 0 then
pa.ar[i].y:= 0;
end;
for i:= 0 to pred(FillPointsCount) do
begin
dec(fps^[i].y, 100);
if fps^[i].y < 0 then
fps^[i].y:= 0;
end;
end;
if (canFlip and (getrandom(2) = 0)) then
begin
for i:= 0 to pred(BasePointsCount) do
pa.ar[i].y:= LAND_HEIGHT - 1 - pa.ar[i].y;
for i:= 0 to pred(FillPointsCount) do
fps^[i].y:= LAND_HEIGHT - 1 - fps^[i].y;
end;
end
end;
procedure Distort1(var Template: TEdgeTemplate; var pa: TPixAr);
var i: Longword;
begin
for i:= 1 to Template.BezierizeCount do
begin
BezierizeEdge(pa, _0_5);
RandomizePoints(pa);
RandomizePoints(pa)
end;
for i:= 1 to Template.RandPassesCount do
RandomizePoints(pa);
BezierizeEdge(pa, _0_1);
end;
procedure FindPoint(si: LongInt; fillPointsCount: LongWord; var newPoint: TPoint; var pa: TPixAr);
const mapBorderMargin = 40;
var p1, p2, p4, fp, mp: TPoint;
i, t1, t2, iy, ix, aqpb: LongInt;
a, b, p, q: LongInt;
dab, d, distL, distR: LongInt;
begin
// [p1, p2] is the segment we're trying to divide
p1:= pa.ar[si];
p2:= pa.ar[si + 1];
if p2.x = NTPX then
// it is segment from last to first point, so need to find first point
begin
i:= si - 2;
while (i >= 0) and (pa.ar[i].x <> NTPX) do
dec(i);
p2:= pa.ar[i + 1]
end;
// perpendicular vector
a:= p2.y - p1.y;
b:= p1.x - p2.x;
dab:= DistanceI(a, b).Round;
// its middle point
mp.x:= (p1.x + p2.x) div 2;
mp.y:= (p1.y + p2.y) div 2;
// don't process too short segments or those which are too close to map borders
if (p1.x = NTPX)
or (dab < minDistance * 3)
or (mp.x < LongInt(leftX) + mapBorderMargin)
or (mp.x > LongInt(rightX) - mapBorderMargin)
or (mp.y < LongInt(topY) + mapBorderMargin)
or (mp.y > LongInt(LAND_HEIGHT) - mapBorderMargin)
then
begin
newPoint:= p1;
exit;
end;
// find distances to map borders
if a <> 0 then
begin
// left border
iy:= (LongInt(leftX) + mapBorderMargin - mp.x) * b div a + mp.y;
d:= DistanceI(mp.x - leftX - mapBorderMargin, mp.y - iy).Round;
t1:= a * (mp.x - mapBorderMargin) + b * (mp.y - iy);
if t1 > 0 then distL:= d else distR:= d;
// right border
iy:= (LongInt(rightX) - mapBorderMargin - mp.x) * b div a + mp.y;
d:= DistanceI(mp.x - rightX + mapBorderMargin, mp.y - iy).Round;
if t1 > 0 then distR:= d else distL:= d;
end else
begin
distL:= LAND_WIDTH + LAND_HEIGHT;
distR:= distL;
end;
if b <> 0 then
begin
// top border
ix:= (LongInt(topY) + mapBorderMargin - mp.y) * a div b + mp.x;
d:= DistanceI(mp.y - topY - mapBorderMargin, mp.x - ix).Round;
t2:= b * (mp.y - mapBorderMargin) + a * (mp.x - ix);
if t2 > 0 then distL:= min(d, distL) else distR:= min(d, distR);
// bottom border
ix:= (LAND_HEIGHT - mapBorderMargin - mp.y) * a div b + mp.x;
d:= DistanceI(mp.y - LAND_HEIGHT + mapBorderMargin, mp.x - ix).Round;
if t2 > 0 then distR:= min(d, distR) else distL:= min(d, distL);
end;
// now go through all other segments
fp:= pa.ar[0];
for i:= 0 to LongInt(pa.Count) - 2 do
if pa.ar[i].x = NTPX then
fp:= pa.ar[i + 1]
else if (i <> si) then
begin
p4:= pa.ar[i + 1];
if p4.x = NTPX then
p4:= fp;
// check if it intersects
t1:= (mp.x - pa.ar[i].x) * b - a * (mp.y - pa.ar[i].y);
t2:= (mp.x - p4.x) * b - a * (mp.y - p4.y);
if (t1 > 0) <> (t2 > 0) then // yes it does, hard arith follows
begin
p:= p4.x - pa.ar[i].x;
q:= p4.y - pa.ar[i].y;
aqpb:= a * q - p * b;
if (aqpb <> 0) then
begin
// (ix; iy) is intersection point
iy:= (((Int64(pa.ar[i].x) - mp.x) * b + Int64(mp.y) * a) * q - Int64(pa.ar[i].y) * p * b) div aqpb;
if abs(b) > abs(q) then
ix:= (iy - mp.y) * a div b + mp.x
else
ix:= (iy - pa.ar[i].y) * p div q + pa.ar[i].x;
d:= DistanceI(mp.y - iy, mp.x - ix).Round;
t1:= b * (mp.y - iy) + a * (mp.x - ix);
if t1 > 0 then distL:= min(d, distL) else distR:= min(d, distR);
end;
end;
end;
// go through all points, including fill points
for i:= 0 to Pred(LongInt(pa.Count + fillPointsCount)) do
// if this point isn't on current segment
if (si <> i) and (i <> si + 1) and (pa.ar[i].x <> NTPX) then
begin
// also check intersection with rays through pa.ar[i] if this point is good
t1:= (p1.x - pa.ar[i].x) * b - a * (p1.y - pa.ar[i].y);
t2:= (p2.x - pa.ar[i].x) * b - a * (p2.y - pa.ar[i].y);
if (t1 > 0) <> (t2 > 0) then
begin
// ray from p1
p:= pa.ar[i].x - p1.x;
q:= pa.ar[i].y - p1.y;
aqpb:= a * q - p * b;
if (aqpb <> 0) then
begin
// (ix; iy) is intersection point
iy:= (((Int64(p1.x) - mp.x) * b + Int64(mp.y) * a) * q - Int64(p1.y) * p * b) div aqpb;
if abs(b) > abs(q) then
ix:= (iy - mp.y) * a div b + mp.x
else
ix:= (iy - p1.y) * p div q + p1.x;
d:= DistanceI(mp.y - iy, mp.x - ix).Round;
t1:= b * (mp.y - iy) + a * (mp.x - ix);
if t1 > 0 then distL:= min(d, distL) else distR:= min(d, distR);
end;
// and ray from p2
p:= pa.ar[i].x - p2.x;
q:= pa.ar[i].y - p2.y;
aqpb:= a * q - p * b;
if (aqpb <> 0) then
begin
// (ix; iy) is intersection point
iy:= (((Int64(p2.x) - mp.x) * b + Int64(mp.y) * a) * q - Int64(p2.y) * p * b) div aqpb;
if abs(b) > abs(q) then
ix:= (iy - mp.y) * a div b + mp.x
else
ix:= (iy - p2.y) * p div q + p2.x;
d:= DistanceI(mp.y - iy, mp.x - ix).Round;
t2:= b * (mp.y - iy) + a * (mp.x - ix);
if t2 > 0 then distL:= min(d, distL) else distR:= min(d, distR);
end;
end;
end;
// don't move new point for more than length of initial segment
// adjust/parametrize for more flat surfaces (try values 3/4, 1/2 of dab, or even 1/4)
d:= dab * 100 div dabDiv;
//d:= dab * (1 + abs(cFeatureSize - 8)) div 6;
//d:= dab * (14 + cFeatureSize) div 20;
if distL > d then distL:= d;
if distR > d then distR:= d;
if distR + distL < minDistance * 2 + 10 then
begin
// limits are too narrow, just divide
newPoint.x:= mp.x;
newPoint.y:= mp.y;
end
else
begin
// select distance within [-distL; distR]
d:= -distL + minDistance + LongInt(GetRandom(distR + distL - minDistance * 2));
//d:= distR - minDistance;
//d:= - distL + minDistance;
// calculate new point
newPoint.x:= mp.x + a * d div dab;
newPoint.y:= mp.y + b * d div dab;
end;
end;
procedure DivideEdges(fillPointsCount: LongWord; var pa: TPixAr);
var i, t: LongInt;
newPoint: TPoint;
begin
newPoint.x:= 0;
newPoint.y:= 0;
i:= 0;
while i < LongInt(pa.Count) - 1 do
begin
FindPoint(i, fillPointsCount, newPoint, pa);
if (newPoint.x <> pa.ar[i].x) or (newPoint.y <> pa.ar[i].y) then
begin
// point found, free a slot for it in array, don't forget to move appended fill points
for t:= pa.Count + fillPointsCount downto i + 2 do
pa.ar[t]:= pa.ar[t - 1];
inc(pa.Count);
pa.ar[i + 1]:= newPoint;
inc(i)
end;
inc(i)
end;
end;
procedure Distort2(var Template: TEdgeTemplate; fps: PPointArray; var pa: TPixAr);
var i: Longword;
begin
// append fill points to ensure distortion won't move them to other side of segment
for i:= 0 to pred(Template.FillPointsCount) do
begin
pa.ar[pa.Count + i].x:= fps^[i].x;
pa.ar[pa.Count + i].y:= fps^[i].y;
end;
// divide while it divides
repeat
i:= pa.Count;
DivideEdges(Template.FillPointsCount, pa)
until i = pa.Count;
{$IFDEF IPHONEOS}
if GameType <> gmtLandPreview then
{$ENDIF}
// make it smooth
BezierizeEdge(pa, _0_2);
end;
procedure GenTemplated(var Template: TEdgeTemplate);
var pa: TPixAr;
i: Longword;
y, x: Longword;
fps: TPointArray;
begin
fps:=Template.FillPoints^;
ResizeLand(Template.TemplateWidth, Template.TemplateHeight);
for y:= 0 to LAND_HEIGHT - 1 do
for x:= 0 to LAND_WIDTH - 1 do
Land[y, x]:= lfBasic;
minDistance:= sqr(cFeatureSize) div 8 + 10;
//dabDiv:= getRandom(41)+60;
//dabDiv:= getRandom(31)+70;
dabDiv:= getRandom(21)+100;
MaxHedgehogs:= Template.MaxHedgehogs;
hasGirders:= Template.hasGirders;
playHeight:= Template.TemplateHeight;
playWidth:= Template.TemplateWidth;
leftX:= (LAND_WIDTH - playWidth) div 2;
rightX:= Pred(leftX + playWidth);
topY:= LAND_HEIGHT - playHeight;
{$HINTS OFF}
SetPoints(Template, pa, @fps);
{$HINTS ON}
Distort2(Template, @fps, pa);
DrawEdge(pa, 0);
with Template do
for i:= 0 to pred(FillPointsCount) do
with fps[i] do
FillLand(x, y, 0, 0);
DrawEdge(pa, lfBasic);
// HACK: force to only cavern even if a cavern map is invertable if cTemplateFilter = 4 ?
if (cTemplateFilter = 4)
or (Template.canInvert and (getrandom(2) = 0))
or (not Template.canInvert and Template.isNegative) then
begin
hasBorder:= true;
for y:= 0 to LAND_HEIGHT - 1 do
for x:= 0 to LAND_WIDTH - 1 do
if (y < topY) or (x < leftX) or (x > rightX) then
Land[y, x]:= 0
else
begin
if Land[y, x] = 0 then
Land[y, x]:= lfBasic
else if Land[y, x] = lfBasic then
Land[y, x]:= 0;
end;
end;
end;
end.