Keep rope/jetpack/parachute selected when destroyed and having secondary ammo selected
Assumption: You rope with secondary ammo selected. You miss a shot,
destroying the rope gear.
Previous behaviour: Rope gets deselected and the secondary
ammo gets selected, you can no longer rope. Very annoying,
reason of many Shoppa fails!
New behaviour: Rope stays selected, but selection of secondary ammo
is cleared (because rope gear got destroyed). Makes much more
sense overall.
Analog for jetpack and parachute.
(* * 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"}unit uUtils;interfaceuses uTypes, uFloat;// returns s with whitespaces (chars <= #32) removed form both endsfunction Trim(s: shortstring) : shortstring;procedure SplitBySpace(var a, b: shortstring);procedure SplitByChar(var a, b: shortstring; c: char);procedure SplitByCharA(var a, b: ansistring; c: char);function ExtractFileDir(s: shortstring) : shortstring;function ExtractFileName(s: shortstring) : shortstring;function EnumToStr(const en : TGearType) : shortstring; overload;function EnumToStr(const en : TVisualGearType) : shortstring; overload;function EnumToStr(const en : TSound) : shortstring; overload;function EnumToStr(const en : TAmmoType) : shortstring; overload;function EnumToStr(const en : TStatInfoType) : shortstring; overload;function EnumToStr(const en : THogEffect) : shortstring; overload;function EnumToStr(const en : TCapGroup) : shortstring; overload;function EnumToStr(const en : TSprite) : shortstring; overload;function EnumToStr(const en : TMapGen) : shortstring; overload;function EnumToStr(const en : TWorldEdge) : shortstring; overload;function Min(a, b: LongInt): LongInt; inline;function MinD(a, b: double) : double; inline;function Max(a, b: LongInt): LongInt; inline;function IntToStr(n: LongInt): shortstring;function StrToInt(s: shortstring): LongInt;function FloatToStr(n: hwFloat): shortstring;function DxDy2Angle(const _dY, _dX: hwFloat): real; inline;function DxDy2Angle32(const _dY, _dX: hwFloat): LongInt;function DxDy2AttackAngle(const _dY, _dX: hwFloat): LongInt;function DxDy2AttackAnglef(const _dY, _dX: extended): LongInt;procedure SetLittle(var r: hwFloat);function Str2PChar(const s: shortstring): PChar;function DecodeBase64(s: shortstring): shortstring;function isPowerOf2(i: Longword): boolean;function toPowerOf2(i: Longword): Longword; inline;function endian(independent: LongWord): LongWord; inline;function CheckCJKFont(s: ansistring; font: THWFont): THWFont;procedure AddFileLog(s: shortstring);procedure AddFileLogRaw(s: pchar); cdecl;function CheckNoTeamOrHH: boolean; inline;function GetLaunchX(at: TAmmoType; dir: LongInt; angle: LongInt): LongInt;function GetLaunchY(at: TAmmoType; angle: LongInt): LongInt;function read1stLn(filePath: shortstring): shortstring;function readValueFromINI(key, filePath: shortstring): shortstring;{$IFNDEF PAS2C}procedure Write(var f: textfile; s: shortstring);procedure WriteLn(var f: textfile; s: shortstring);function StrLength(s: PChar): Longword;procedure SetLengthA(var s: ansistring; len: Longword);{$ENDIF}function isPhone: Boolean; inline;{$IFDEF IPHONEOS}procedure startLoadingIndicator; cdecl; external;procedure stopLoadingIndicator; cdecl; external;procedure saveFinishedSynching; cdecl; external;function isApplePhone: Boolean; cdecl; external;procedure AudioServicesPlaySystemSound(num: LongInt); cdecl; external;{$ENDIF}function sanitizeForLog(s: shortstring): shortstring;function sanitizeCharForLog(c: char): shortstring;procedure initModule(isNotPreview: boolean);procedure freeModule;implementationuses {$IFNDEF PAS2C}typinfo, {$ENDIF}Math, uConsts, uVariables, uPhysFSLayer, uDebug;{$IFDEF DEBUGFILE}var logFile: PFSFile;{$IFDEF USE_VIDEO_RECORDING} logMutex: TRTLCriticalSection; // mutex for debug file{$ENDIF}{$ENDIF}var CharArray: array[0..255] of Char;// All leading/tailing characters with ordinal values less than or equal to 32 (a space) are stripped.function Trim(s: shortstring) : shortstring;var len, left, right: integer;beginlen:= Length(s);if len = 0 then exit(s);// find first non-whitespaceleft:= 1;while left <= len do begin if s[left] > #32 then break; inc(left); end;// find last non-whitespaceright:= len;while right >= 1 do begin if s[right] > #32 then break; dec(right); end;// string is whitespace onlyif left > right then exit('');// get string without surrounding whitespacelen:= right - left + 1;Trim:= copy(s, left, len);end;function GetLastSlashPos(var s: shortString) : integer;var lslash: integer; c: char;begin// find last slashlslash:= Length(s);while lslash >= 1 do begin c:= s[lslash]; if (c = #47) or (c = #92) then break; dec(lslash); end;GetLastSlashPos:= lslash;end;function ExtractFileDir(s: shortstring) : shortstring;var lslash: byte;beginif Length(s) = 0 then exit(s);lslash:= GetLastSlashPos(s);if lslash <= 1 then exit('');s[0]:= char(lslash - 1);ExtractFileDir:= s;end;function ExtractFileName(s: shortstring) : shortstring;var lslash, len: byte;beginlen:= Length(s);if len = 0 then exit(s);lslash:= GetLastSlashPos(s);if lslash < 1 then exit(s);if lslash = len then exit('');len:= len - lslash;ExtractFilename:= copy(s, lslash + 1, len);end;procedure SplitBySpace(var a,b: shortstring);beginSplitByChar(a,b,' ');end;// should this include "strtolower()" for the split string?procedure SplitByChar(var a, b: shortstring; c : char);var i, t: LongInt;begini:= Pos(c, a);if i > 0 then begin for t:= 1 to Pred(i) do if (a[t] >= 'A')and(a[t] <= 'Z') then Inc(a[t], 32); b:= copy(a, i + 1, Length(a) - i); a[0]:= char(Pred(i)) {$IFDEF PAS2C} a[i] := 0; {$ENDIF} endelse b:= '';end;{$IFNDEF PAS2C}procedure SetLengthA(var s: ansistring; len: Longword);begin SetLength(s, len)end;{$ENDIF}procedure SplitByCharA(var a, b: ansistring; c: char);var i: LongInt;begini:= Pos(c, a);if i > 0 then begin b:= copy(a, i + 1, Length(a) - i); SetLengthA(a, Pred(i)); end else b:= '';end; { SplitByCharA }function EnumToStr(const en : TGearType) : shortstring; overload;beginEnumToStr:= GetEnumName(TypeInfo(TGearType), ord(en))end;function EnumToStr(const en : TVisualGearType) : shortstring; overload;beginEnumToStr:= GetEnumName(TypeInfo(TVisualGearType), ord(en))end;function EnumToStr(const en : TSound) : shortstring; overload;beginEnumToStr:= GetEnumName(TypeInfo(TSound), ord(en))end;function EnumToStr(const en : TAmmoType) : shortstring; overload;beginEnumToStr:= GetEnumName(TypeInfo(TAmmoType), ord(en))end;function EnumToStr(const en : TStatInfoType) : shortstring; overload;beginEnumToStr:= GetEnumName(TypeInfo(TStatInfoType), ord(en))end;function EnumToStr(const en: THogEffect) : shortstring; overload;beginEnumToStr := GetEnumName(TypeInfo(THogEffect), ord(en))end;function EnumToStr(const en: TCapGroup) : shortstring; overload;beginEnumToStr := GetEnumName(TypeInfo(TCapGroup), ord(en))end;function EnumToStr(const en: TSprite) : shortstring; overload;beginEnumToStr := GetEnumName(TypeInfo(TSprite), ord(en))end;function EnumToStr(const en: TMapGen) : shortstring; overload;beginEnumToStr := GetEnumName(TypeInfo(TMapGen), ord(en))end;function EnumToStr(const en: TWorldEdge) : shortstring; overload;beginEnumToStr := GetEnumName(TypeInfo(TWorldEdge), ord(en))end;function Min(a, b: LongInt): LongInt;beginif a < b then Min:= aelse Min:= bend;function MinD(a, b: double): double;beginif a < b then MinD:= aelse MinD:= bend;function Max(a, b: LongInt): LongInt;beginif a > b then Max:= aelse Max:= bend;function IntToStr(n: LongInt): shortstring;beginstr(n, IntToStr)end;function StrToInt(s: shortstring): LongInt;beginval(s, StrToInt);end;function FloatToStr(n: hwFloat): shortstring;beginFloatToStr:= cstr(n) + '_' + inttostr(Lo(n.QWordValue))end;function DxDy2Angle(const _dY, _dX: hwFloat): real; inline;var dY, dX: Extended;begindY:= hwFloat2Float(_dY);dX:= hwFloat2Float(_dX);DxDy2Angle:= arctan2(dY, dX) * 180 / piend;function DxDy2Angle32(const _dY, _dX: hwFloat): LongInt;const _16divPI: Extended = 16/pi;var dY, dX: Extended;begindY:= hwFloat2Float(_dY);dX:= hwFloat2Float(_dX);DxDy2Angle32:= trunc(arctan2(dY, dX) * _16divPI) and $1fend;function DxDy2AttackAngle(const _dY, _dX: hwFloat): LongInt;const MaxAngleDivPI: Extended = cMaxAngle/pi;var dY, dX: Extended;begindY:= hwFloat2Float(_dY);dX:= hwFloat2Float(_dX);DxDy2AttackAngle:= trunc(arctan2(dY, dX) * MaxAngleDivPI)end;function DxDy2AttackAnglef(const _dY, _dX: extended): LongInt; inline;beginDxDy2AttackAnglef:= trunc(arctan2(_dY, _dX) * (cMaxAngle/pi))end;procedure SetLittle(var r: hwFloat);beginr:= SignAs(cLittle, r)end;function isPowerOf2(i: Longword): boolean;beginisPowerOf2:= (i and (i - 1)) = 0end;function toPowerOf2(i: Longword): Longword;begintoPowerOf2:= 1;while (toPowerOf2 < i) do toPowerOf2:= toPowerOf2 shl 1end;function DecodeBase64(s: shortstring): shortstring;const table = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';var i, t, c: LongInt;beginc:= 0;for i:= 1 to Length(s) do begin t:= Pos(s[i], table); if s[i] = '=' then inc(c); if t > 0 then s[i]:= char(t - 1) else s[i]:= #0 end;i:= 1;t:= 1;while i <= length(s) do begin DecodeBase64[t ]:= char((byte(s[i ]) shl 2) or (byte(s[i + 1]) shr 4)); DecodeBase64[t + 1]:= char((byte(s[i + 1]) shl 4) or (byte(s[i + 2]) shr 2)); DecodeBase64[t + 2]:= char((byte(s[i + 2]) shl 6) or (byte(s[i + 3]) )); inc(t, 3); inc(i, 4) end;if c < 3 then t:= t - c;DecodeBase64[0]:= char(t - 1)end;function Str2PChar(const s: shortstring): PChar;var i :Integer ;begin for i:= 1 to Length(s) do begin CharArray[i - 1] := s[i]; end; CharArray[Length(s)]:= #0; Str2PChar:= @(CharArray[0]);end;function endian(independent: LongWord): LongWord; inline;begin{$IFDEF ENDIAN_LITTLE}endian:= independent;{$ELSE}endian:= (((independent and $FF000000) shr 24) or ((independent and $00FF0000) shr 8) or ((independent and $0000FF00) shl 8) or ((independent and $000000FF) shl 24)){$ENDIF}end;procedure AddFileLog(s: shortstring);begin// s:= s;{$IFDEF DEBUGFILE}{$IFDEF USE_VIDEO_RECORDING}EnterCriticalSection(logMutex);{$ENDIF}if logFile <> nil then pfsWriteLn(logFile, inttostr(GameTicks) + ': ' + s)else WriteLn(stdout, inttostr(GameTicks) + ': ' + s);{$IFDEF USE_VIDEO_RECORDING}LeaveCriticalSection(logMutex);{$ENDIF}{$ENDIF}end;procedure AddFileLogRaw(s: pchar); cdecl;begins:= s;{$IFNDEF PAS2C}{$IFDEF DEBUGFILE}{$IFDEF USE_VIDEO_RECORDING}EnterCriticalSection(logMutex);{$ENDIF}// TODO: uncomment next two lines// write(logFile, s);// flush(logFile);{$IFDEF USE_VIDEO_RECORDING}LeaveCriticalSection(logMutex);{$ENDIF}{$ENDIF}{$ENDIF}end;function CheckCJKFont(s: ansistring; font: THWFont): THWFont;var l, i : LongInt; u: WideChar; tmpstr: array[0..256] of WideChar;beginCheckCJKFont:= font;{$IFNDEF MOBILE}// remove chinese fonts for nowif (font >= CJKfnt16) or (length(s) = 0) then{$ENDIF} exit;l:= Utf8ToUnicode(PWideChar(@tmpstr), PChar(s), min(length(tmpstr), length(s)))-1;i:= 0;while i < l do begin u:= tmpstr[i]; if (#$1100 <= u) and ( (u <= #$11FF ) or // Hangul Jamo ((#$2E80 <= u) and (u <= #$2FDF)) or // CJK Radicals Supplement / Kangxi Radicals ((#$2FF0 <= u) and (u <= #$31FF)) or // Ideographic Description Characters / CJK Radicals Supplement / Hiragana / Hangul Compatibility Jamo / Katakana ((#$31C0 <= u) and (u <= #$31EF)) or // CJK Strokes ((#$3200 <= u) and (u <= #$4DBF)) or // Enclosed CJK Letters and Months / CJK Compatibility / CJK Unified Ideographs Extension A / Circled Katakana ((#$4E00 <= u) and (u <= #$9FFF)) or // CJK Unified Ideographs ((#$AC00 <= u) and (u <= #$D7AF)) or // Hangul Syllables ((#$F900 <= u) and (u <= #$FAFF)) or // CJK Compatibility Ideographs ((#$FE30 <= u) and (u <= #$FE4F)) or // CJK Compatibility Forms ((#$FF66 <= u) and (u <= #$FF9D))) // halfwidth katakana then begin CheckCJKFont:= THWFont( ord(font) + ((ord(High(THWFont))+1) div 2) ); exit; end; inc(i) end;(* two more to check. pascal WideChar is only 16 bit though ((#$20000 <= u) and (u >= #$2A6DF)) or // CJK Unified Ideographs Extension B ((#$2F800 <= u) and (u >= #$2FA1F))) // CJK Compatibility Ideographs Supplement *)end;function GetLaunchX(at: TAmmoType; dir: LongInt; angle: LongInt): LongInt;beginat:= at; dir:= dir; angle:= angle; // parameter hint suppression because code below is currently disabledGetLaunchX:= 0(* if (Ammoz[at].ejectX <> 0) or (Ammoz[at].ejectY <> 0) then GetLaunchX:= sign(dir) * (8 + hwRound(AngleSin(angle) * Ammoz[at].ejectX) + hwRound(AngleCos(angle) * Ammoz[at].ejectY)) else GetLaunchX:= 0 *)end;function GetLaunchY(at: TAmmoType; angle: LongInt): LongInt;beginat:= at; angle:= angle; // parameter hint suppression because code below is currently disabledGetLaunchY:= 0(* if (Ammoz[at].ejectX <> 0) or (Ammoz[at].ejectY <> 0) then GetLaunchY:= hwRound(AngleSin(angle) * Ammoz[at].ejectY) - hwRound(AngleCos(angle) * Ammoz[at].ejectX) - 2 else GetLaunchY:= 0*)end;function CheckNoTeamOrHH: boolean;beginCheckNoTeamOrHH:= (CurrentTeam = nil) or (CurrentHedgehog^.Gear = nil);end;{$IFNDEF PAS2C}procedure Write(var f: textfile; s: shortstring);beginsystem.write(f, s)end;procedure WriteLn(var f: textfile; s: shortstring);beginsystem.writeln(f, s)end;function StrLength(s: PChar): Longword;begin StrLength:= length(s)end;{$ENDIF}// this function is just to determine whether we are running on a limited screen devicefunction isPhone: Boolean; inline;begin isPhone:= false;{$IFDEF IPHONEOS} isPhone:= isApplePhone();{$ENDIF}{$IFDEF ANDROID} //nasty nasty hack. TODO: implement callback to java to have a unified way of determining if it is a tablet if (cScreenWidth < 1000) and (cScreenHeight < 500) then isPhone:= true;{$ENDIF}end;function sanitizeForLog(s: shortstring): shortstring;var i: byte; r: shortstring;begin r[0]:= s[0]; for i:= 1 to length(s) do if (s[i] < #32) or (s[i] > #127) then r[i]:= '?' else r[i]:= s[i]; sanitizeForLog:= rend;function sanitizeCharForLog(c: char): shortstring;var r: shortstring;begin if (c < #32) or (c > #127) then r:= '#' + inttostr(byte(c)) else begin // some magic for pas2c r[0]:= #1; r[1]:= c; end; sanitizeCharForLog:= rend;function read1stLn(filePath: shortstring): shortstring;var f: pfsFile;begin read1stLn:= ''; if pfsExists(filePath) then begin f:= pfsOpenRead(filePath); if (not pfsEOF(f)) and allOK then pfsReadLn(f, read1stLn); pfsClose(f); f:= nil; end;end;function readValueFromINI(key, filePath: shortstring): shortstring;var f: pfsFile; s: shortstring; i: LongInt;begin s:= ''; readValueFromINI:= ''; if pfsExists(filePath) then begin f:= pfsOpenRead(filePath); while (not pfsEOF(f)) and allOK do begin pfsReadLn(f, s); if Length(s) = 0 then continue; if s[1] = ';' then continue; i:= Pos('=', s); if Trim(Copy(s, 1, Pred(i))) = key then begin Delete(s, 1, i); readValueFromINI:= s; end; end; pfsClose(f); f:= nil; end;end;procedure initModule(isNotPreview: boolean);{$IFDEF DEBUGFILE}var logfileBase: shortstring; i: LongInt;{$ENDIF}begin{$IFDEF DEBUGFILE} if isNotPreview then begin if GameType = gmtRecord then logfileBase:= 'rec' else {$IFDEF PAS2C} logfileBase:= 'game_pas2c'; {$ELSE} logfileBase:= 'game'; {$ENDIF} end else {$IFDEF PAS2C} logfileBase:= 'preview_pas2c'; {$ELSE} logfileBase:= 'preview'; {$ENDIF}{$IFDEF USE_VIDEO_RECORDING} InitCriticalSection(logMutex);{$ENDIF} if not pfsExists('/Logs') then pfsMakeDir('/Logs'); // if log is locked, write to the next one i:= 0; while(i < 7) do begin logFile:= pfsOpenWrite('/Logs/' + logfileBase + inttostr(i) + '.log'); if logFile <> nil then break; inc(i) end; if logFile = nil then WriteLn(stdout, '[WARNING] Could not open log file for writing. Log will be written to stdout!');{$ENDIF} //mobile stuff{$IFDEF IPHONEOS} mobileRecord.PerformRumble:= @AudioServicesPlaySystemSound; mobileRecord.GameLoading:= @startLoadingIndicator; mobileRecord.GameLoaded:= @stopLoadingIndicator; mobileRecord.SaveLoadingEnded:= @saveFinishedSynching;{$ELSE} mobileRecord.PerformRumble:= nil; mobileRecord.GameLoading:= nil; mobileRecord.GameLoaded:= nil; mobileRecord.SaveLoadingEnded:= nil;{$ENDIF}end;procedure freeModule;begin{$IFDEF DEBUGFILE} pfsWriteLn(logFile, 'halt at ' + inttostr(GameTicks) + ' ticks. TurnTimeLeft = ' + inttostr(TurnTimeLeft)); pfsFlush(logFile); pfsClose(logFile);{$IFDEF USE_VIDEO_RECORDING} DoneCriticalSection(logMutex);{$ENDIF}{$ENDIF}end;end.