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.
(*
* Hedgewars, a free turn based strategy game
* Copyright (c) 2004-2015 Andrey Korotaev <unC0Rr@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 2 of the License
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*)
{$INCLUDE "options.inc"}
{$IF GLunit = GL}{$DEFINE GLunit:=GL,GLext}{$ENDIF}
unit uStore;
interface
uses SysUtils, uConsts, SDLh, GLunit, uTypes, uLandTexture, uCaptions, uChat;
procedure initModule;
procedure freeModule;
procedure LoadFonts();
procedure StoreLoad(reload: boolean);
procedure StoreRelease(reload: boolean);
procedure RenderHealth(var Hedgehog: THedgehog);
function makeHealthBarTexture(w, h, Color: Longword): PTexture;
procedure AddProgress;
procedure FinishProgress;
function LoadImage(const filename: shortstring; imageFlags: LongInt): PSDL_Surface;
// loads an image from the games data files
function LoadDataImage(const path: TPathType; const filename: shortstring; imageFlags: LongInt): PSDL_Surface;
// like LoadDataImage but uses altPath as fallback-path if file not found/loadable in path
function LoadDataImageAltPath(const path, altPath: TPathType; const filename: shortstring; imageFlags: LongInt): PSDL_Surface;
// like LoadDataImage but uses altFile as fallback-filename if file cannot be loaded
function LoadDataImageAltFile(const path: TPathType; const filename, altFile: shortstring; imageFlags: LongInt): PSDL_Surface;
procedure LoadHedgehogHat(var HH: THedgehog; newHat: shortstring);
procedure LoadHedgehogHat2(var HH: THedgehog; newHat: shortstring; allowSurfReuse: boolean);
procedure InitZoom(zoom: real);
procedure SetupOpenGL;
function RenderHelpWindow(caption, subcaption, description, extra: ansistring; extracolor: LongInt; iconsurf: PSDL_Surface; iconrect: PSDL_Rect): PTexture;
procedure RenderWeaponTooltip(atype: TAmmoType);
procedure ShowWeaponTooltip(x, y: LongInt);
procedure MakeCrossHairs;
{$IFDEF USE_VIDEO_RECORDING}
procedure InitOffscreenOpenGL;
{$ENDIF}
procedure WarpMouse(x, y: Word); inline;
procedure SwapBuffers; {$IFDEF USE_VIDEO_RECORDING}cdecl{$ELSE}inline{$ENDIF};
procedure SetSkyColor(r, g, b: real);
implementation
uses uMisc, uConsole, uVariables, uUtils, uTextures, uRender, uRenderUtils,
uCommands, uPhysFSLayer, uDebug, uLocale, uInputHandler, adler32
{$IFDEF USE_CONTEXT_RESTORE}, uWorld{$ENDIF};
//type TGPUVendor = (gvUnknown, gvNVIDIA, gvATI, gvIntel, gvApple);
var
squaresize : LongInt;
numsquares : LongInt;
ProgrTex: PTexture;
LoadingText: PTexture;
prevHat: shortstring;
tmpHatSurf: PSDL_Surface;
const
cHHFileName = 'Hedgehog';
cCHFileName = 'Crosshair';
procedure freeTmpHatSurf();
begin
if tmpHatSurf = nil then exit;
SDL_FreeSurface(tmpHatSurf);
tmpHatSurf:= nil;
prevHat:= 'NoHat';
end;
procedure InitZoom(zoom: real);
begin
SetScale(zoom);
// make sure view limits are updated
// because SetScale() doesn't do it, if zoom=cScaleFactor
updateViewLimits();
end;
function WriteInRect(Surface: PSDL_Surface; X, Y: LongInt; Color: LongWord; Font: THWFont; s: PChar): TSDL_Rect;
var w, h: LongInt;
tmpsurf: PSDL_Surface;
clr: TSDL_Color;
finalRect: TSDL_Rect;
begin
w:= 0; h:= 0; // avoid compiler hints
TTF_SizeUTF8(Fontz[Font].Handle, s, @w, @h);
finalRect.x:= X + cFontBorder + 2;
finalRect.y:= Y + cFontBorder;
finalRect.w:= w + cFontBorder * 2 + 4;
finalRect.h:= h + cFontBorder * 2;
clr.r:= Color shr 16;
clr.g:= (Color shr 8) and $FF;
clr.b:= Color and $FF;
tmpsurf:= TTF_RenderUTF8_Blended(Fontz[Font].Handle, s, clr);
if tmpsurf = nil then exit;
tmpsurf:= doSurfaceConversion(tmpsurf);
if tmpsurf <> nil then
begin
SDL_UpperBlit(tmpsurf, nil, Surface, @finalRect);
SDL_FreeSurface(tmpsurf);
finalRect.x:= X;
finalRect.y:= Y;
finalRect.w:= w + cFontBorder * 2 + 4;
finalRect.h:= h + cFontBorder * 2;
end;
WriteInRect:= finalRect
end;
procedure MakeCrossHairs;
var tmpsurf: PSDL_Surface;
begin
tmpsurf:= LoadDataImage(ptGraphics, cCHFileName, ifAlpha or ifCritical);
CrosshairTexture:= Surface2Tex(tmpsurf, false);
SDL_FreeSurface(tmpsurf)
end;
function makeHealthBarTexture(w, h, Color: Longword): PTexture;
var
rr: TSDL_Rect;
texsurf: PSDL_Surface;
begin
rr.x:= 0;
rr.y:= 0;
rr.w:= w;
rr.h:= h;
texsurf:= SDL_CreateRGBSurface(SDL_SWSURFACE, w, h, 32, RMask, GMask, BMask, AMask);
if not checkFails(texsurf <> nil, errmsgCreateSurface, true) then
checkFails(SDL_SetColorKey(texsurf, SDL_TRUE, 0) = 0, errmsgTransparentSet, true);
if not allOK then exit(nil);
DrawRoundRect(@rr, cWhiteColor, cNearBlackColor, texsurf, true);
rr.x:= 2;
rr.y:= 2;
rr.w:= w - 4;
rr.h:= h - 4;
DrawRoundRect(@rr, Color, Color, texsurf, false);
makeHealthBarTexture:= Surface2Tex(texsurf, false);
SDL_FreeSurface(texsurf);
end;
procedure WriteNames(Font: THWFont);
var t: LongInt;
i, maxLevel: LongInt;
r: TSDL_Rect;
drY: LongInt;
texsurf, flagsurf, iconsurf: PSDL_Surface;
foundBot: boolean;
year, month, md : word;
begin
if cOnlyStats then exit;
r.x:= 0;
r.y:= 0;
drY:= - 4;
{$IFNDEF PAS2C}
DecodeDate(Date, year, month, md);
{$ELSE}
year:= 0;
month:= 0;
md:= 0;
{$ENDIF}
for t:= 0 to Pred(TeamsCount) do
with TeamsArray[t]^ do
begin
if ExtDriven then
NameTagTex:= RenderStringTexLim(ansistring(TeamName), Clan^.Color, Font, cTeamHealthWidth)
else NameTagTex:= RenderStringTex(ansistring(TeamName), Clan^.Color, Font);
if length(Owner) > 0 then
if ExtDriven then
OwnerTex:= RenderStringTexLim(ansistring(Owner), Clan^.Color, Font, cTeamHealthWidth)
else OwnerTex:= RenderStringTex(ansistring(Owner), Clan^.Color, Font);
r.x:= 0;
r.y:= 0;
r.w:= 32;
r.h:= 32;
texsurf:= SDL_CreateRGBSurface(SDL_SWSURFACE, r.w, r.h, 32, RMask, GMask, BMask, AMask);
if not checkFails(texsurf <> nil, errmsgCreateSurface, true) then
checkFails(SDL_SetColorKey(texsurf, SDL_TRUE, 0) = 0, errmsgTransparentSet, true);
if not allOK then exit;
r.w:= 26;
r.h:= 19;
DrawRoundRect(@r, cWhiteColor, cNearBlackColor, texsurf, true);
// overwrite flag for cpu teams and keep players from using it
foundBot:= false;
maxLevel:= -1;
for i:= 0 to cMaxHHIndex do
with Hedgehogs[i] do
if (Gear <> nil) and (BotLevel > 0) then
begin
foundBot:= true;
// initially was going to do the highest botlevel of the team, but for now, just apply if entire team has same bot level
if maxLevel = -1 then maxLevel:= BotLevel
else if (maxLevel > 0) and (maxLevel <> BotLevel) then maxLevel:= 0;
//if (maxLevel > 0) and (BotLevel < maxLevel) then maxLevel:= BotLevel
end
else if Gear <> nil then maxLevel:= 0;
if foundBot then
begin
// disabled the plain flag - I think it looks ok even w/ full bars obscuring CPU
//if (maxLevel > 0) and (maxLevel < 3) then Flag:= 'cpu_plain' else
Flag:= 'cpu'
end
else if (Flag = 'cpu') or (Flag = 'cpu_plain') then
Flag:= 'hedgewars';
flagsurf:= LoadDataImageAltFile(ptFlags, Flag, 'hedgewars', ifNone);
if not checkFails(flagsurf <> nil, 'Failed to load flag "' + Flag + '" as well as the default flag', true) then
begin
case maxLevel of
1: copyToXY(SpritesData[sprBotlevels].Surface, flagsurf, 0, 0);
2: copyToXYFromRect(SpritesData[sprBotlevels].Surface, flagsurf, 5, 2, 17, 13, 5, 2);
3: copyToXYFromRect(SpritesData[sprBotlevels].Surface, flagsurf, 9, 5, 13, 10, 9, 5);
4: copyToXYFromRect(SpritesData[sprBotlevels].Surface, flagsurf, 13, 9, 9, 6, 13, 9);
5: copyToXYFromRect(SpritesData[sprBotlevels].Surface, flagsurf, 17, 11, 5, 4, 17, 11)
end;
copyToXY(flagsurf, texsurf, 2, 2);
SDL_FreeSurface(flagsurf);
flagsurf:= nil;
end;
// restore black border pixels inside the flag
PLongwordArray(texsurf^.pixels)^[32 * 2 + 2]:= cNearBlackColor;
PLongwordArray(texsurf^.pixels)^[32 * 2 + 23]:= cNearBlackColor;
PLongwordArray(texsurf^.pixels)^[32 * 16 + 2]:= cNearBlackColor;
PLongwordArray(texsurf^.pixels)^[32 * 16 + 23]:= cNearBlackColor;
FlagTex:= Surface2Tex(texsurf, false);
SDL_FreeSurface(texsurf);
texsurf:= nil;
if not allOK then exit;
AIKillsTex := RenderStringTex(ansistring(inttostr(stats.AIKills)), Clan^.Color, fnt16);
LuaTeamValueTex := RenderStringTex(LuaTeamValue, Clan^.Color, fnt16);
dec(drY, r.h + 2);
DrawHealthY:= drY;
for i:= 0 to cMaxHHIndex do
with Hedgehogs[i] do
if Gear <> nil then
begin
if ExtDriven then
NameTagTex:= RenderStringTexLim(ansistring(Name), Clan^.Color, fnt16, cTeamHealthWidth)
else NameTagTex:= RenderStringTex(ansistring(Name), Clan^.Color, fnt16);
if Hat = 'NoHat' then
begin
if (month = 4) and (md = 20) then
Hat := 'eastertop' // Easter
else if (month = 12) and ((md = 24) or (md = 25) or (md = 26)) then
Hat := 'Santa' // Christmas Eve/Christmas/Boxing Day
else if (month = 10) and (md = 31) then
Hat := 'fr_pumpkin'; // Halloween/Hedgewars' birthday
end;
if (month = 4) and (md = 1) then
begin
AprilOne:= true;
Hat := 'fr_tomato'; // avoid promoting violence to hedgehogs. see http://hedgewars.org/node/5818
end;
if Hat <> 'NoHat' then
begin
if (Length(Hat) > 39) and (Copy(Hat,1,8) = 'Reserved') and (Copy(Hat,9,32) = PlayerHash) then
LoadHedgehogHat2(Hedgehogs[i], 'Reserved/' + Copy(Hat,9,Length(Hat)-8), true)
else
LoadHedgehogHat2(Hedgehogs[i], Hat, true);
end
end;
end;
freeTmpHatSurf();
MissionIcons:= LoadDataImage(ptGraphics, 'missions', ifCritical);
iconsurf:= SDL_CreateRGBSurface(SDL_SWSURFACE, 28, 28, 32, RMask, GMask, BMask, AMask);
if iconsurf <> nil then
begin
r.x:= 0;
r.y:= 0;
r.w:= 28;
r.h:= 28;
DrawRoundRect(@r, cWhiteColor, cNearBlackColor, iconsurf, true);
ropeIconTex:= Surface2Tex(iconsurf, false);
SDL_FreeSurface(iconsurf);
iconsurf:= nil;
end;
for t:= 0 to Pred(ClansCount) do
with ClansArray[t]^ do
HealthTex:= makeHealthBarTexture(cTeamHealthWidth + 5, Teams[0]^.NameTagTex^.h, Color);
GenericHealthTexture:= makeHealthBarTexture(cTeamHealthWidth + 5, TeamsArray[0]^.NameTagTex^.h, cWhiteColor)
end;
procedure InitHealth;
var i, t: LongInt;
begin
for t:= 0 to Pred(TeamsCount) do
if TeamsArray[t] <> nil then
with TeamsArray[t]^ do
begin
for i:= 0 to cMaxHHIndex do
if Hedgehogs[i].Gear <> nil then
RenderHealth(Hedgehogs[i]);
end
end;
procedure LoadGraves;
var t: LongInt;
texsurf: PSDL_Surface;
begin
for t:= 0 to Pred(TeamsCount) do
if TeamsArray[t] <> nil then
with TeamsArray[t]^ do
begin
if GraveName = '' then
GraveName:= 'Statue';
texsurf:= LoadDataImageAltFile(ptGraves, GraveName, 'Statue', ifCritical or ifColorKey);
GraveTex:= Surface2Tex(texsurf, false);
SDL_FreeSurface(texsurf)
end
end;
procedure LoadFonts();
var s: shortstring;
fi: THWFont;
begin
AddFileLog('LoadFonts();');
if (not cOnlyStats) then
for fi:= Low(THWFont) to High(THWFont) do
with Fontz[fi] do
begin
s:= cPathz[ptFonts] + '/' + Name;
WriteToConsole(msgLoading + s + ' (' + inttostr(Height) + 'pt)... ');
Handle:= TTF_OpenFontRW(rwopsOpenRead(s), true, Height);
if SDLCheck(Handle <> nil, 'TTF_OpenFontRW', true) then exit;
TTF_SetFontStyle(Handle, style);
WriteLnToConsole(msgOK)
end;
end;
procedure StoreLoad(reload: boolean);
var ii: TSprite;
ai: TAmmoType;
tmpsurf, tmpoverlay: PSDL_Surface;
i, y, imflags: LongInt;
keyConfirm, keyQuit: ansistring;
begin
AddFileLog('StoreLoad()');
if not cOnlyStats then
begin
MakeCrossHairs;
LoadGraves;
{$IFDEF IPHONEOS}
tmpHatSurf:= LoadDataImage(ptHats, 'chef', ifNone);
{$ELSE}
tmpHatSurf:= LoadDataImage(ptHats, 'Reserved/chef', ifNone);
{$ENDIF}
ChefHatTexture:= Surface2Tex(tmpHatSurf, true);
freeTmpHatSurf();
end;
if not reload then
AddProgress;
for ii:= Low(TSprite) to High(TSprite) do
with SpritesData[ii] do
// FIXME - add a sprite attribute to match on rq flags?
if (((cReducedQuality and (rqNoBackground or rqLowRes)) = 0) or // why rqLowRes?
(not (ii in [sprSky, sprSkyL, sprSkyR, sprHorizont, sprHorizontL, sprHorizontR])))
and (((cReducedQuality and rqPlainSplash) = 0) or ((not (ii in [sprSplash, sprDroplet, sprSDSplash, sprSDDroplet]))))
and (((cReducedQuality and rqKillFlakes) = 0) or cSnow or ((not (ii in [sprFlake, sprSDFlake]))))
and ((cCloudsNumber > 0) or (ii <> sprCloud))
and ((vobCount > 0) or (ii <> sprFlake))
and (savesurf or (not cOnlyStats)) // in stats-only only load those which are needed later
and allOK
then
begin
if reload then
tmpsurf:= Surface
else
begin
imflags := (ifAlpha or ifColorKey);
// these sprites are optional
if critical then
imflags := (imflags or ifCritical);
// load the image
tmpsurf := LoadDataImageAltPath(Path, AltPath, FileName, imflags);
if (tmpsurf <> nil) and checkSum then
for y := 0 to tmpsurf^.h-1 do
syncedPixelDigest:= Adler32Update(syncedPixelDigest, @PByteArray(tmpsurf^.pixels)^[y*tmpsurf^.pitch], tmpsurf^.w*4)
end;
if tmpsurf <> nil then
begin
if getImageDimensions then
begin
imageWidth:= tmpsurf^.w;
imageHeight:= tmpsurf^.h
end;
if getDimensions then
if Height = -1 then //BlueWater
begin
Width:= tmpsurf^.w;
Height:= tmpsurf^.h div watFrames;
end
else if Height = -2 then //SDWater
begin
Width:= tmpsurf^.w;
Height:= tmpsurf^.h div watSDFrames;
end
else
begin
Width:= tmpsurf^.w;
Height:= tmpsurf^.h
end;
if (ii in [sprAMAmmos, sprAMAmmosBW]) then
begin
tmpoverlay := LoadDataImage(Path, copy(FileName, 1, length(FileName)-5), (imflags and (not ifCritical)));
if tmpoverlay <> nil then
begin
copyToXY(tmpoverlay, tmpsurf, 0, 0);
SDL_FreeSurface(tmpoverlay)
end
end;
if (ii in [sprSky, sprSkyL, sprSkyR, sprHorizont, sprHorizontL, sprHorizontR]) then
begin
Texture:= Surface2Tex(tmpsurf, true);
Texture^.Scale:= 2
end
else
begin
Texture:= Surface2Tex(tmpsurf, false);
// HACK: We should include some sprite attribute to define the texture wrap directions
if ((ii = sprWater) or (ii = sprSDWater)) and ((cReducedQuality and (rq2DWater or rqClampLess)) = 0) then
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
end;
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_PRIORITY, priority);
// This should maybe be flagged. It wastes quite a bit of memory.
if not reload then
begin
{$IFDEF USE_CONTEXT_RESTORE}
Surface:= tmpsurf
{$ELSE}
if saveSurf then
Surface:= tmpsurf
else
SDL_FreeSurface(tmpsurf)
{$ENDIF}
end
end
else
Surface:= nil
end;
if (not cOnlyStats) and allOK then
begin
WriteNames(fnt16);
if not reload then
AddProgress;
tmpsurf:= LoadDataImage(ptGraphics, cHHFileName, ifAlpha or ifCritical or ifColorKey);
HHTexture:= Surface2Tex(tmpsurf, false);
SDL_FreeSurface(tmpsurf);
InitHealth;
PauseTexture:= RenderStringTex(trmsg[sidPaused], cYellowColor, fntBig);
AFKTexture:= RenderStringTex(trmsg[sidAFK], cYellowColor, fntBig);
keyConfirm:= KeyBindToName('confirm');
keyQuit:= KeyBindToName('quit');
ConfirmTexture:= RenderStringTex(FormatA(trmsg[sidConfirm], [keyConfirm, keyQuit]), cYellowColor, fntBig);
SyncTexture:= RenderStringTex(trmsg[sidSync], cYellowColor, fntBig);
if not reload then
AddProgress;
// name of weapons in ammo menu
for ai:= Low(TAmmoType) to High(TAmmoType) do
with Ammoz[ai] do
begin
if checkFails(length(trAmmo[NameId]) > 0,'No default text/translation found for ammo type #' + intToStr(ord(ai)) + '!',true) then exit;
tmpsurf:= TTF_RenderUTF8_Blended(Fontz[CheckCJKFont(trAmmo[NameId],fnt16)].Handle, PChar(trAmmo[NameId]), cWhiteColorChannels);
if checkFails(tmpsurf <> nil,'Name-texture creation for ammo type #' + intToStr(ord(ai)) + ' failed!',true) then exit;
tmpsurf:= doSurfaceConversion(tmpsurf);
FreeAndNilTexture(NameTex);
NameTex:= Surface2Tex(tmpsurf, false);
SDL_FreeSurface(tmpsurf)
end;
// number of weapons in ammo menu
for i:= Low(CountTexz) to High(CountTexz) do
begin
tmpsurf:= TTF_RenderUTF8_Blended(Fontz[fnt16].Handle, Str2PChar(IntToStr(i) + 'x'), cWhiteColorChannels);
tmpsurf:= doSurfaceConversion(tmpsurf);
FreeAndNilTexture(CountTexz[i]);
CountTexz[i]:= Surface2Tex(tmpsurf, false);
SDL_FreeSurface(tmpsurf)
end;
if not reload then
AddProgress;
end;
IMG_Quit();
end;
procedure StoreRelease(reload: boolean);
var ii: TSprite;
ai: TAmmoType;
i, t: LongInt;
begin
for ii:= Low(TSprite) to High(TSprite) do
begin
FreeAndNilTexture(SpritesData[ii].Texture);
if (SpritesData[ii].Surface <> nil) and (not reload) then
begin
SDL_FreeSurface(SpritesData[ii].Surface);
SpritesData[ii].Surface:= nil
end
end;
SDL_FreeSurface(MissionIcons);
// free the textures declared in uVariables
FreeAndNilTexture(ChefHatTexture);
FreeAndNilTexture(CrosshairTexture);
FreeAndNilTexture(WeaponTooltipTex);
FreeAndNilTexture(PauseTexture);
FreeAndNilTexture(AFKTexture);
FreeAndNilTexture(SyncTexture);
FreeAndNilTexture(ConfirmTexture);
FreeAndNilTexture(ropeIconTex);
FreeAndNilTexture(HHTexture);
FreeAndNilTexture(GenericHealthTexture);
// free all ammo name textures
for ai:= Low(TAmmoType) to High(TAmmoType) do
FreeAndNilTexture(Ammoz[ai].NameTex);
// free all count textures
for i:= Low(CountTexz) to High(CountTexz) do
begin
FreeAndNilTexture(CountTexz[i]);
CountTexz[i]:= nil
end;
for t:= 0 to Pred(ClansCount) do
begin
if ClansArray[t] <> nil then
FreeAndNilTexture(ClansArray[t]^.HealthTex);
end;
// free all team and hedgehog textures
for t:= 0 to Pred(TeamsCount) do
begin
if TeamsArray[t] <> nil then
begin
FreeAndNilTexture(TeamsArray[t]^.NameTagTex);
FreeAndNilTexture(TeamsArray[t]^.GraveTex);
FreeAndNilTexture(TeamsArray[t]^.AIKillsTex);
FreeAndNilTexture(TeamsArray[t]^.FlagTex);
for i:= 0 to cMaxHHIndex do
begin
FreeAndNilTexture(TeamsArray[t]^.Hedgehogs[i].NameTagTex);
FreeAndNilTexture(TeamsArray[t]^.Hedgehogs[i].HealthTagTex);
FreeAndNilTexture(TeamsArray[t]^.Hedgehogs[i].HatTex);
end;
end;
end;
RendererCleanup();
end;
procedure RenderHealth(var Hedgehog: THedgehog);
var s: shortstring;
begin
s:= IntToStr(Hedgehog.Gear^.Health);
FreeAndNilTexture(Hedgehog.HealthTagTex);
Hedgehog.HealthTagTex:= RenderStringTex(ansistring(s), Hedgehog.Team^.Clan^.Color, fnt16)
end;
function LoadImage(const filename: shortstring; imageFlags: LongInt): PSDL_Surface;
var tmpsurf: PSDL_Surface;
s: shortstring;
rwops: PSDL_RWops;
begin
LoadImage:= nil;
WriteToConsole(msgLoading + filename + '.png [flags: ' + inttostr(imageFlags) + '] ');
s:= filename + '.png';
rwops:= nil;
tmpsurf:= nil;
if pfsExists(s) then
begin
// get data source
rwops:= rwopsOpenRead(s);
// load image with SDL (with freesrc param set to true)
if rwops <> nil then
tmpsurf:= IMG_Load_RW(rwops, true);
end;
// loading failed
if tmpsurf = nil then
begin
// output sdl error if loading failed when data source was available
if rwops <> nil then
begin
// anounce that loading failed
OutError(msgFailed, false);
if SDLCheck(false, 'LoadImage', (imageFlags and ifCritical) <> 0) then exit;
// rwops was already freed by IMG_Load_RW
rwops:= nil;
end else
OutError(msgFailed, (imageFlags and ifCritical) <> 0);
exit;
end;
if ((imageFlags and ifIgnoreCaps) = 0) and ((tmpsurf^.w > MaxTextureSize) or (tmpsurf^.h > MaxTextureSize)) then
begin
SDL_FreeSurface(tmpsurf);
OutError(msgFailedSize, ((not cOnlyStats) and ((imageFlags and ifCritical) <> 0)));
// dummy surface to replace non-critical textures that failed to load due to their size
LoadImage:= SDL_CreateRGBSurface(SDL_SWSURFACE, 2, 2, 32, RMask, GMask, BMask, AMask);
exit;
end;
tmpsurf:= doSurfaceConversion(tmpsurf);
if (imageFlags and ifColorKey) <> 0 then
if checkFails(SDL_SetColorKey(tmpsurf, SDL_TRUE, 0) = 0, errmsgTransparentSet, true) then exit;
WriteLnToConsole(msgOK + ' (' + inttostr(tmpsurf^.w) + 'x' + inttostr(tmpsurf^.h) + ')');
LoadImage:= tmpsurf //Result
end;
function LoadDataImage(const path: TPathType; const filename: shortstring; imageFlags: LongInt): PSDL_Surface;
var tmpsurf: PSDL_Surface;
begin
// check for file in user dir (never critical)
tmpsurf:= LoadImage(cPathz[path] + '/' + filename, imageFlags);
LoadDataImage:= tmpsurf;
end;
function LoadDataImageAltPath(const path, altPath: TPathType; const filename: shortstring; imageFlags: LongInt): PSDL_Surface;
var tmpsurf: PSDL_Surface;
begin
// if there is no alternative path, just forward and return result
if (altPath = ptNone) then
exit(LoadDataImage(path, filename, imageFlags));
// since we have a fallback path this search isn't critical yet
tmpsurf:= LoadDataImage(path, filename, imageFlags and (not ifCritical));
// if image still not found try alternative path
if (tmpsurf = nil) then
tmpsurf:= LoadDataImage(altPath, filename, imageFlags);
LoadDataImageAltPath:= tmpsurf;
end;
function LoadDataImageAltFile(const path: TPathType; const filename, altFile: shortstring; imageFlags: LongInt): PSDL_Surface;
var tmpsurf: PSDL_Surface;
begin
// if there is no alternative filename, just forward and return result
if (altFile = '') then
exit(LoadDataImage(path, filename, imageFlags));
// since we have a fallback filename this search isn't critical yet
tmpsurf:= LoadDataImage(path, filename, imageFlags and (not ifCritical));
// if image still not found try alternative filename
if (tmpsurf = nil) then
tmpsurf:= LoadDataImage(path, altFile, imageFlags);
LoadDataImageAltFile:= tmpsurf;
end;
procedure LoadHedgehogHat(var HH: THedgehog; newHat: shortstring);
begin
LoadHedgehogHat2(HH, newHat, false);
end;
procedure LoadHedgehogHat2(var HH: THedgehog; newHat: shortstring; allowSurfReuse: boolean);
begin
// free the mem of any previously assigned texture. This was previously only if the new one could be loaded, but, NoHat is usually a better choice
if HH.HatTex <> nil then
FreeAndNilTexture(HH.HatTex);
// load new hat surface if this hat is different than the one already loaded
if newHat <> prevHat then
begin
freeTmpHatSurf();
tmpHatSurf:= LoadDataImage(ptHats, newHat, ifNone);
end;
AddFileLog('Hat => '+newHat);
// only do something if the hat could be loaded
if tmpHatSurf <> nil then
begin
AddFileLog('Got Hat');
// assign new hat to hedgehog
HH.HatTex:= Surface2Tex(tmpHatSurf, true);
// remember that this hat was used last
if allowSurfReuse then
prevHat:= newHat
// cleanup: free temporary surface mem
else
freeTmpHatSurf();
end;
end;
procedure SetupOpenGLAttributes;
begin
{$IFDEF IPHONEOS}
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 0);
SDL_GL_SetAttribute(SDL_GL_RETAINED_BACKING, 1);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 1);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 1);
{$ELSE}
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
{$ENDIF}
SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 5);
SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 6);
SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 5);
SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 0); // no depth buffer
SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, 0); // no alpha channel
SDL_GL_SetAttribute(SDL_GL_BUFFER_SIZE, 16); // buffer should be 16
SDL_GL_SetAttribute(SDL_GL_ACCELERATED_VISUAL, 1); // prefer hw rendering
end;
procedure SetupOpenGL;
begin
AddFileLog('Setting up OpenGL (using driver: ' + shortstring(SDL_GetCurrentVideoDriver()) + ')');
// TODO: this function creates an opengles1.1 context
// un-comment below and add proper logic to support opengles2.0
//SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2);
//SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0);
if SDLGLcontext = nil then
SDLGLcontext:= SDL_GL_CreateContext(SDLwindow);
if SDLCheck(SDLGLcontext <> nil, 'SDLGLcontext', true) then exit;
SDL_GL_SetSwapInterval(1);
RendererSetup();
// gl2 init/matrix code was here, but removed
end;
////////////////////////////////////////////////////////////////////////////////
procedure AddProgress;
var r: TSDL_Rect;
texsurf: PSDL_Surface;
begin
if cOnlyStats then exit;
if Step = 0 then
begin
WriteToConsole(msgLoading + 'progress sprite: ');
texsurf:= LoadDataImage(ptGraphics, 'Progress', ifCritical or ifColorKey);
ProgrTex:= Surface2Tex(texsurf, false);
LoadingText:= RenderStringTex(trmsg[sidLoading], $FFF39EE8, fntBig);
squaresize:= texsurf^.w shr 1;
numsquares:= texsurf^.h div squaresize;
SDL_FreeSurface(texsurf);
{$IFNDEF PAS2C}
with mobileRecord do
if GameLoading <> nil then
GameLoading();
{$ENDIF}
end;
if checkFails(ProgrTex <> nil, 'Error - Progress Texure is nil!', true) then exit;
RenderClear();
if Step < numsquares then
r.x:= 0
else
r.x:= squaresize;
r.y:= (Step mod numsquares) * squaresize;
r.w:= squaresize;
r.h:= squaresize;
DrawTextureFromRect( -squaresize div 2, (cScreenHeight - squaresize) shr 1, @r, ProgrTex);
DrawTexture( -LoadingText^.w div 2, (cScreenHeight - LoadingText^.h) shr 1 - (squaresize div 2) - (LoadingText^.h div 2) - 8, LoadingText);
SwapBuffers;
inc(Step);
end;
procedure FinishProgress;
begin
{$IFNDEF PAS2C}
with mobileRecord do
if GameLoaded <> nil then
GameLoaded();
{$ENDIF}
WriteLnToConsole('Freeing progress textures... ');
FreeAndNilTexture(ProgrTex);
FreeAndNilTexture(LoadingText);
Step:= 0
end;
function RenderHelpWindow(caption, subcaption, description, extra: ansistring; extracolor: LongInt; iconsurf: PSDL_Surface; iconrect: PSDL_Rect): PTexture;
var tmpsurf: PSDL_SURFACE;
w, h, i, j: LongInt;
font: THWFont;
r, r2: TSDL_Rect;
wa, ha: LongInt;
tmpline, tmpline2, tmpdesc: ansistring;
begin
// make sure there is a caption as well as a sub caption - description is optional
if length(caption) = 0 then
caption:= ansistring('???');
if length(subcaption) = 0 then
subcaption:= ansistring(_S' ');
font:= CheckCJKFont(caption,fnt16);
font:= CheckCJKFont(subcaption,font);
font:= CheckCJKFont(description,font);
font:= CheckCJKFont(extra,font);
w:= 0;
h:= 0;
wa:= cFontBorder * 2 + 4;
ha:= cFontBorder * 2;
i:= 0; j:= 0; // avoid compiler hints
// TODO: Recheck height/position calculation
// get caption's dimensions
TTF_SizeUTF8(Fontz[font].Handle, PChar(caption), @i, @j);
// width adds 36 px (image + space)
w:= i + 36 + wa;
h:= j + ha;
// get sub caption's dimensions
TTF_SizeUTF8(Fontz[font].Handle, PChar(subcaption), @i, @j);
// width adds 36 px (image + space)
if w < (i + 36 + wa) then
w:= i + 36 + wa;
inc(h, j + ha);
// get description's dimensions
tmpdesc:= description;
while length(tmpdesc) > 0 do
begin
tmpline:= tmpdesc;
SplitByCharA(tmpline, tmpdesc, '|');
if length(tmpline) > 0 then
begin
TTF_SizeUTF8(Fontz[font].Handle, PChar(tmpline), @i, @j);
if w < (i + wa) then
w:= i + wa;
inc(h, j + ha)
end
end;
if length(extra) > 0 then
begin
// get extra label's dimensions
TTF_SizeUTF8(Fontz[font].Handle, PChar(extra), @i, @j);
if w < (i + wa) then
w:= i + wa;
inc(h, j + ha);
end;
// add borders space
inc(w, wa);
inc(h, ha + 8);
tmpsurf:= SDL_CreateRGBSurface(SDL_SWSURFACE, w, h, 32, RMask, GMask, BMask, AMask);
if checkFails(tmpsurf <> nil, 'RenderHelpWindow: fail to create surface', true) then exit(nil);
// render border and background
r.x:= 0;
r.y:= 0;
r.w:= w;
r.h:= h;
DrawRoundRect(@r, cWhiteColor, cNearBlackColor, tmpsurf, true);
// render caption
r:= WriteInRect(tmpsurf, 36 + cFontBorder + 2, ha, $ffffffff, font, PChar(caption));
// render sub caption
r:= WriteInRect(tmpsurf, 36 + cFontBorder + 2, r.y + r.h, $ffc7c7c7, font, PChar(subcaption));
// render all description lines
tmpdesc:= description;
while length(tmpdesc) > 0 do
begin
tmpline:= tmpdesc;
SplitByCharA(tmpline, tmpdesc, '|');
r2:= r;
if length(tmpline) > 0 then
begin
r:= WriteInRect(tmpsurf, cFontBorder + 2, r.y + r.h, $ff707070, font, PChar(tmpline));
// render highlighted caption (if there is a ':')
tmpline2:= _S'';
SplitByCharA(tmpline, tmpline2, ':');
if length(tmpline2) > 0 then
begin
tmpline:= tmpline + ':';
WriteInRect(tmpsurf, cFontBorder + 2, r2.y + r2.h, $ffc7c7c7, font, PChar(tmpline));
end;
end
end;
if length(extra) > 0 then
r:= WriteInRect(tmpsurf, cFontBorder + 2, r.y + r.h, extracolor, font, PChar(extra));
r.x:= cFontBorder + 6;
r.y:= cFontBorder + 4;
r.w:= 32;
r.h:= 32;
SDL_FillRect(tmpsurf, @r, $ff000000);
SDL_UpperBlit(iconsurf, iconrect, tmpsurf, @r);
RenderHelpWindow:= Surface2Tex(tmpsurf, true);
SDL_FreeSurface(tmpsurf)
end;
procedure RenderWeaponTooltip(atype: TAmmoType);
var r: TSDL_Rect;
i: LongInt;
ammoname: ansistring;
ammocap: ansistring;
ammodesc: ansistring;
extra: ansistring;
extracolor: LongInt;
begin
// don't do anything if the window shouldn't be shown
if (cReducedQuality and rqTooltipsOff) <> 0 then
begin
WeaponTooltipTex:= nil;
exit
end;
// free old texture
FreeAndNilTexture(WeaponTooltipTex);
// image region
i:= LongInt(atype) - 1;
r.x:= (i shr 4) * 32;
r.y:= (i mod 16) * 32;
r.w:= 32;
r.h:= 32;
// default (no extra text)
extra:= _S'';
extracolor:= 0;
if (CurrentTeam <> nil) and (Ammoz[atype].SkipTurns >= CurrentTeam^.Clan^.TurnNumber) then // weapon or utility is not yet available
begin
if (atype = amTardis) and (SuddenDeathActive) then
extra:= trmsg[sidNotAvailableInSD]
else
extra:= trmsg[sidNotYetAvailable];
extracolor:= LongInt($ffc77070);
end
else if (Ammoz[atype].Ammo.Propz and ammoprop_NoRoundEnd) <> 0 then // weapon or utility will not end your turn
begin
extra:= trmsg[sidNoEndTurn];
extracolor:= LongInt($ff70c770);
end
else
begin
extra:= _S'';
extracolor:= 0;
end;
if length(trluaammo[Ammoz[atype].NameId]) > 0 then
ammoname := trluaammo[Ammoz[atype].NameId]
else
ammoname := trammo[Ammoz[atype].NameId];
if length(trluaammoc[Ammoz[atype].NameId]) > 0 then
ammocap := trluaammoc[Ammoz[atype].NameId]
else
ammocap := trammoc[Ammoz[atype].NameId];
if length(trluaammod[Ammoz[atype].NameId]) > 0 then
ammodesc := trluaammod[Ammoz[atype].NameId]
else
ammodesc := trammod[Ammoz[atype].NameId];
if length(trluaammoa[Ammoz[atype].NameId]) > 0 then
ammodesc := ammodesc + '|' + trluaammoa[Ammoz[atype].NameId];
// render window and return the texture
WeaponTooltipTex:= RenderHelpWindow(ammoname, ammocap, ammodesc, extra, extracolor, SpritesData[sprAMAmmos].Surface, @r)
end;
procedure ShowWeaponTooltip(x, y: LongInt);
begin
// draw the texture if it exists
if WeaponTooltipTex <> nil then
DrawTexture(x, y, WeaponTooltipTex)
end;
{$IFDEF USE_VIDEO_RECORDING}
procedure InitOffscreenOpenGL;
begin
// create hidden window
SDLwindow:= SDL_CreateWindow(PChar('hedgewars video rendering (SDL2 hidden window)'),
SDL_WINDOWPOS_CENTERED_MASK, SDL_WINDOWPOS_CENTERED_MASK,
cScreenWidth, cScreenHeight,
SDL_WINDOW_HIDDEN or SDL_WINDOW_OPENGL);
if SDLCheck(SDLwindow <> nil, 'SDL_CreateWindow', true) then exit;
SetupOpenGL();
end;
{$ENDIF} // USE_VIDEO_RECORDING
procedure chFullScr(var s: shortstring);
var flags: Longword = 0;
reinit: boolean = false;
{$IFNDEF DARWIN}ico: PSDL_Surface;{$ENDIF}
x, y: LongInt;
begin
if cOnlyStats then
begin
MaxTextureSize:= 1024;
exit
end;
if Length(s) = 0 then
cFullScreen:= (not cFullScreen)
else cFullScreen:= s = '1';
if cFullScreen then
begin
cScreenWidth:= cFullscreenWidth;
cScreenHeight:= cFullscreenHeight;
end
else
begin
cScreenWidth:= cWindowedWidth;
cScreenHeight:= cWindowedHeight;
end;
AddFileLog('Preparing to change video parameters...');
if SDLwindow = nil then
begin
// set window title
WriteToConsole('Init SDL_image... ');
if SDLCheck(IMG_Init(IMG_INIT_PNG) <> 0, 'IMG_Init', true) then exit;
WriteLnToConsole(msgOK);
end
else
begin
AmmoMenuInvalidated:= true;
{$IFDEF IPHONEOS}
// chFullScr is called when there is a rotation event and needs the SetScale and SetupOpenGL to set up the new resolution
// this 6 gl functions are the relevant ones and are hacked together here for optimisation
glMatrixMode(GL_MODELVIEW);
glPopMatrix;
glLoadIdentity();
glScalef(2.0 / cScreenWidth, -2.0 / cScreenHeight, 1.0);
glTranslatef(0, -cScreenHeight / 2, 0);
glViewport(0, 0, cScreenWidth, cScreenHeight);
exit;
{$ELSE}
SetScale(cDefaultZoomLevel);
{$IFDEF USE_CONTEXT_RESTORE}
reinit:= true;
StoreRelease(true);
ResetLand;
ResetWorldTex;
//uTextures.freeModule; //DEBUG ONLY
{$ENDIF}
AddFileLog('Freeing old primary surface...');
{$ENDIF}
end;
// these attributes must be set up before creating the sdl window
{$IFNDEF WIN32}
(* On a large number of testers machines, SDL default to software rendering
when opengl attributes were set. These attributes were "set" after
CreateWindow in .15, which probably did nothing.
IMO we should rely on the gl_config defaults from SDL, and use
SDL_GL_GetAttribute to possibly post warnings if any bad values are set.
*)
SetupOpenGLAttributes();
{$ENDIF}
// these values in x and y make the window appear in the center
x:= SDL_WINDOWPOS_CENTERED_MASK;
y:= SDL_WINDOWPOS_CENTERED_MASK;
if SDLwindow = nil then
begin
// SDL_WINDOW_RESIZABLE makes the window resizable and
// respond to rotation events on mobile devices
flags:= SDL_WINDOW_OPENGL or SDL_WINDOW_SHOWN or SDL_WINDOW_RESIZABLE;
{$IFDEF MOBILE}
if isPhone() then
SDL_SetHint('SDL_IOS_ORIENTATIONS','LandscapeLeft LandscapeRight');
// no need for borders on mobile devices
flags:= flags or SDL_WINDOW_BORDERLESS;
{$ENDIF}
if cFullScreen then
flags:= flags or SDL_WINDOW_FULLSCREEN;
SDLwindow:= SDL_CreateWindow(PChar('Hedgewars'), x, y, cScreenWidth, cScreenHeight, flags);
end
// we're toggling
else if Length(s) = 0 then
begin
if cFullScreen then
begin
SDL_SetWindowSize(SDLwindow, cScreenWidth, cScreenHeight);
SDL_SetWindowFullscreen(SDLwindow, SDL_WINDOW_FULLSCREEN);
end
else
begin
SDL_SetWindowFullscreen(SDLwindow, 0);
SDL_SetWindowSize(SDLwindow, cScreenWidth, cScreenHeight);
SDL_SetWindowPosition(SDLwindow, x, y);
end;
updateViewLimits();
end;
if SDLCheck(SDLwindow <> nil, 'SDL_CreateWindow', true) then exit;
// load engine ico
{$IFNDEF DARWIN}
ico:= LoadDataImage(ptGraphics, 'hwengine', ifIgnoreCaps);
if ico <> nil then
begin
SDL_SetWindowIcon(SDLwindow, ico);
SDL_FreeSurface(ico);
end;
{$ENDIF}
SetupOpenGL();
if reinit then
begin
// clean the window from any previous content
RenderClear();
if SuddenDeathDmg then
SetSkyColor(SDSkyColor.r * (SDTint.r/255) / 255, SDSkyColor.g * (SDTint.g/255) / 255, SDSkyColor.b * (SDTint.b/255) / 255)
else if ((cReducedQuality and rqNoBackground) = 0) then
SetSkyColor(SkyColor.r / 255, SkyColor.g / 255, SkyColor.b / 255)
else
SetSkyColor(RQSkyColor.r / 255, RQSkyColor.g / 255, RQSkyColor.b / 255);
// reload everything we had before
ReloadCaptions(false);
ReloadLines;
StoreLoad(true);
// redraw all land
UpdateLandTexture(0, LAND_WIDTH, 0, LAND_HEIGHT, false);
end;
end;
// for sdl1.2 we directly call SDL_WarpMouse()
// for sdl2 we provide a SDL_WarpMouse() which just calls this function
// this has the advantage of reducing 'uses' and 'ifdef' statements
// (SDLwindow is a private member of this module)
procedure WarpMouse(x, y: Word); inline;
begin
SDL_WarpMouseInWindow(SDLwindow, x, y);
end;
procedure SwapBuffers; {$IFDEF USE_VIDEO_RECORDING}cdecl{$ELSE}inline{$ENDIF};
begin
if GameType = gmtRecord then
exit;
SDL_GL_SwapWindow(SDLwindow);
end;
procedure SetSkyColor(r, g, b: real);
begin
RenderSetClearColor(r, g, b, 0.99)
end;
procedure initModule;
var ai: TAmmoType;
i: LongInt;
begin
RegisterVariable('fullscr', @chFullScr, true);
cScaleFactor:= 2.0;
updateViewLimits();
Step:= 0;
ProgrTex:= nil;
SupportNPOTT:= false;
// init all ammo name texture pointers
for ai:= Low(TAmmoType) to High(TAmmoType) do
begin
Ammoz[ai].NameTex := nil;
end;
// init all count texture pointers
for i:= Low(CountTexz) to High(CountTexz) do
CountTexz[i] := nil;
SDLwindow:= nil;
SDLGLcontext:= nil;
prevHat:= 'NoHat';
tmpHatSurf:= nil;
end;
procedure freeModule;
var fi: THWFont;
begin
StoreRelease(false);
// make sure fonts are cleaned up
for fi:= Low(THWFont) to High(THWFont) do
with Fontz[fi] do
begin
if Handle <> nil then
begin
TTF_CloseFont(Handle);
Handle:= nil;
end;
end;
TTF_Quit();
SDL_GL_DeleteContext(SDLGLcontext);
SDL_DestroyWindow(SDLwindow);
SDL_Quit();
end;
end.