sync: restore source from Google Drive
Sync changed files to Google Drive / sync (push) Successful in 1h45m13s
Sync changed files to Google Drive / sync (push) Successful in 1h45m13s
This commit is contained in:
1 parent
e2d8247e91
commit
7063d1b1c9
993 files changed
+949976
No files matched your search
@@ -0,0 +1,85 @@
|
||||
unit KM_CampaignClasses;
|
||||
{$I KaM_Remake.inc}
|
||||
interface
|
||||
uses
|
||||
KM_CommonClasses;
|
||||
|
||||
type
|
||||
//Unique campaign identification, stored as 3 ANSI letters (TSK, TPR, etc)
|
||||
//3 bytes are used to avoid string types issues
|
||||
TKMCampaignId = class
|
||||
private
|
||||
fID: AnsiString;
|
||||
function GetID: AnsiString;
|
||||
procedure SetID(aID: AnsiString);
|
||||
public
|
||||
constructor Create(aID: AnsiString);
|
||||
constructor Load(aLoadStream: TKMemoryStream);
|
||||
procedure Save(aSaveStream: TKMemoryStream);
|
||||
|
||||
property ID: AnsiString read GetID write SetID;
|
||||
|
||||
class function isIdValid(aID: AnsiString): Boolean;
|
||||
end;
|
||||
|
||||
const
|
||||
CAMPAIGN_ID_LENGTH = 3;
|
||||
|
||||
|
||||
implementation
|
||||
uses
|
||||
SysUtils;
|
||||
|
||||
|
||||
{ TKMCampaignId }
|
||||
constructor TKMCampaignId.Create(aID: AnsiString);
|
||||
begin
|
||||
inherited Create;
|
||||
|
||||
if not isIdValid(aID) then
|
||||
raise Exception.Create('Wrong campaign ID: ' + string(aID));
|
||||
|
||||
fID := aID;
|
||||
end;
|
||||
|
||||
|
||||
constructor TKMCampaignId.Load(aLoadStream: TKMemoryStream);
|
||||
begin
|
||||
inherited Create;
|
||||
|
||||
aLoadStream.ReadA(fID);
|
||||
end;
|
||||
|
||||
|
||||
function TKMCampaignId.GetID: AnsiString;
|
||||
begin
|
||||
if Self = nil then Exit('');
|
||||
|
||||
Result := fID;
|
||||
end;
|
||||
|
||||
|
||||
procedure TKMCampaignId.Save(aSaveStream: TKMemoryStream);
|
||||
begin
|
||||
if Self = nil then Exit;
|
||||
|
||||
aSaveStream.WriteA(fID);
|
||||
end;
|
||||
|
||||
|
||||
procedure TKMCampaignId.SetID(aID: AnsiString);
|
||||
begin
|
||||
if not isIdValid(aID) then
|
||||
raise Exception.Create('Wrong campaign ID: ' + string(aID));
|
||||
|
||||
fID := aID;
|
||||
end;
|
||||
|
||||
|
||||
class function TKMCampaignId.isIdValid(aID: AnsiString): Boolean;
|
||||
begin
|
||||
Result := length(aID) = CAMPAIGN_ID_LENGTH;
|
||||
end;
|
||||
|
||||
|
||||
end.
|
||||
@@ -0,0 +1,14 @@
|
||||
unit KM_CampaignTypes;
|
||||
{$I KaM_Remake.inc}
|
||||
interface
|
||||
|
||||
const
|
||||
CAMPAIGN_DATA_SCRIPT_FILENAME = 'campaigndata';
|
||||
|
||||
CAMPAIGN_SCRIPTS_FOLDER_NAME = 'Scripts';
|
||||
|
||||
CAMPAIGN_SOUNDS_FOLDER_NAME = 'Sounds';
|
||||
|
||||
implementation
|
||||
|
||||
end.
|
||||
@@ -0,0 +1,31 @@
|
||||
unit KM_CampaignUtils;
|
||||
{$I KaM_Remake.inc}
|
||||
interface
|
||||
|
||||
type
|
||||
TKMCampaignUtils = class
|
||||
public
|
||||
class function GetMissionFile(const aPath, aShortName: UnicodeString; aIndex: Byte; const aExt: UnicodeString = '.dat'): String;
|
||||
class function GetMissionName(const aShortName: UnicodeString; aIndex: Byte): String;
|
||||
end;
|
||||
|
||||
|
||||
implementation
|
||||
uses
|
||||
SysUtils;
|
||||
|
||||
|
||||
class function TKMCampaignUtils.GetMissionFile(const aPath, aShortName: UnicodeString; aIndex: Byte; const aExt: UnicodeString = '.dat'): String;
|
||||
begin
|
||||
var missionName := GetMissionName(aShortName, aIndex);
|
||||
Result := aPath + missionName + PathDelim + missionName + aExt;
|
||||
end;
|
||||
|
||||
|
||||
class function TKMCampaignUtils.GetMissionName(const aShortName: UnicodeString; aIndex: Byte): String;
|
||||
begin
|
||||
Result := aShortName + Format('%.2d', [aIndex + 1]);
|
||||
end;
|
||||
|
||||
|
||||
end.
|
||||
+1154
File diff suppressed because it is too large.
Load diff
+105
@@ -0,0 +1,105 @@
|
||||
unit KM_MapTypes;
|
||||
{$I KaM_Remake.inc}
|
||||
interface
|
||||
uses
|
||||
KM_Defaults, KM_CommonTypes, KM_ResTypes;
|
||||
|
||||
type
|
||||
TKMMapKind = (mkUnknown, mkSP, mkMP, mkDL);
|
||||
TKMMapKindSet = set of TKMMapKind;
|
||||
|
||||
// Sketch of the goal and message displaying system used in KaM (from scripting point of view anyway)
|
||||
// This is very similar to that used in KaM and is quite flexable/expandable.
|
||||
// (we can add more parameters/conditions as well as existing KaM ones, possibly using a new script command)
|
||||
// Some things are probably named unclearly, please give me suggestions or change them. Goals are the one part
|
||||
// of scripting that seems to confuse everyone at first, mainly because of the TGoalStatus. In 99% of cases gsTrue and gtDefeat
|
||||
// go together, because the if the defeat conditions is NOT True you lose, not the other way around. I guess it should be called a
|
||||
// "survival" conditions rather than defeat.
|
||||
// I put some examples below to give you an idea of how it works. Remember this is basically a copy of the goal scripting system in KaM,
|
||||
// not something I designed. It can change, this is just easiest to implement from script compatability point of view.
|
||||
TKMGoalType = (
|
||||
gltNone = 0, // Means: It is not required for victory or defeat (e.g. simply display a message)
|
||||
gltVictory, // Means: "The following condition must be True for you to win"
|
||||
gltSurvive // Means: "The following condition must be True or else you lose"
|
||||
);
|
||||
|
||||
// Conditions are the same numbers as in KaM script
|
||||
TKMGoalCondition = (
|
||||
gcUnknown0, // Not used/unknown
|
||||
gcBuildTutorial, // Must build a tannery (and other buildings from tutorial?) for it to be True. In KaM tutorial messages will be dispalyed if this is a goal
|
||||
gcTime, // A certain time must pass
|
||||
gcBuildings, // Storehouse, school, barracks, TownHall
|
||||
gcTroops, // All troops
|
||||
gcUnknown5, // Not used/unknown
|
||||
gcMilitaryAssets, // All Troops, Coal mine, Weapons Workshop, Tannery, Armory workshop, Stables, Iron mine, Iron smithy, Weapons smithy, Armory smithy, Barracks, Town hall and Vehicles Workshop
|
||||
gcSerfsAndSchools, // Serfs (possibly all citizens?) and schoolhouses
|
||||
gcEconomyBuildings // School, Inn and Storehouse
|
||||
//We can come up with our own
|
||||
);
|
||||
|
||||
TKMGoalStatus = (gsTrue = 0, gsFalse = 1); // Weird that it's inverted, but KaM uses it that way
|
||||
|
||||
TKMissionMode = (mmBuilding, mmFighting);
|
||||
|
||||
//* Mission difficulty
|
||||
TKMMissionDifficulty = (mdNone, mdEasy3, mdEasy2, mdEasy1, mdNormal, mdHard1, mdHard2, mdHard3);
|
||||
//* Set of mission difficulties
|
||||
TKMMissionDifficultySet = set of TKMMissionDifficulty;
|
||||
|
||||
const
|
||||
{$I KM_TextIDs.inc}
|
||||
|
||||
GOAL_BUILDINGS_HOUSES: array[0..3] of TKMHouseType = (htStore, htSchool, htBarracks, htTownHall);
|
||||
|
||||
//We discontinue support of other goals in favor of PascalScript scripts
|
||||
GOALS_SUPPORTED: set of TKMGoalCondition =
|
||||
[gcBuildings, gcTroops, gcMilitaryAssets, gcSerfsAndSchools, gcEconomyBuildings];
|
||||
|
||||
// Used for error's logging
|
||||
GOAL_CONDITION_STR: array [TKMGoalCondition] of string = (
|
||||
'Unknown 0',
|
||||
'Build Tannery',
|
||||
'Time',
|
||||
'Store School Barracks Townhall',
|
||||
'Troops',
|
||||
'Unknown 5',
|
||||
'Military assets',
|
||||
'Serfs&Schools',
|
||||
'School Inn Store'
|
||||
);
|
||||
|
||||
|
||||
MISSION_DIFFICULTY_MIN = mdEasy3;
|
||||
MISSION_DIFFICULTY_MAX = mdHard3;
|
||||
|
||||
DIFFICULTY_LEVELS_TX: array[MISSION_DIFFICULTY_MIN..MISSION_DIFFICULTY_MAX] of Integer = (
|
||||
TX_MISSION_DIFFICULTY_EASY3,
|
||||
TX_MISSION_DIFFICULTY_EASY2,
|
||||
TX_MISSION_DIFFICULTY_EASY1,
|
||||
TX_MISSION_DIFFICULTY_NORMAL,
|
||||
TX_MISSION_DIFFICULTY_HARD1,
|
||||
TX_MISSION_DIFFICULTY_HARD2,
|
||||
TX_MISSION_DIFFICULTY_HARD3
|
||||
);
|
||||
|
||||
DIFFICULTY_LEVELS_COLOR: array[TKMMissionDifficulty] of Cardinal = (
|
||||
icLightGray2,
|
||||
icLightGreen,
|
||||
icGreen,
|
||||
icGreenYellow,
|
||||
icYellow,
|
||||
icOrange,
|
||||
icDarkOrange,
|
||||
icRed
|
||||
);
|
||||
|
||||
//Map folder name by folder type. Containing single maps, for SP/MP/DL mode
|
||||
MAP_FOLDER_NAME: array [TKMMapKind] of string = ('', MAPS_FOLDER_NAME, MAPS_MP_FOLDER_NAME, MAPS_DL_FOLDER_NAME);
|
||||
|
||||
CUSTOM_MAP_PARAM_DESCR_TX: array[TKMCustomScriptParam] of Integer = (TX_MAP_CUSTOM_PARAM_TH_TROOP_COST, TX_MAP_CUSTOM_PARAM_MARKET_PRICE);
|
||||
|
||||
|
||||
implementation
|
||||
|
||||
|
||||
end.
|
||||
+66
@@ -0,0 +1,66 @@
|
||||
unit KM_MapUtils;
|
||||
{$I KaM_Remake.inc}
|
||||
interface
|
||||
uses
|
||||
KM_Defaults, KM_MapTypes;
|
||||
|
||||
function GuessMPPathRel(const aName, aExt: string; aCRC: Cardinal): string;
|
||||
function GuessMissionPathRel(const aMissionFileRelSP, aMissionName: string; aMapFullCRC: Cardinal; aIsMultiplayer: Boolean): string;
|
||||
|
||||
function DetermineMapKind(const aFolderName: UnicodeString; out aMapKind: TKMMapKind): Boolean;
|
||||
function GetMapKind(aIsMultiplayer: Boolean): TKMMapKind;
|
||||
|
||||
implementation
|
||||
uses
|
||||
SysUtils;
|
||||
|
||||
|
||||
function GuessMPPathRel(const aName, aExt: string; aCRC: Cardinal): string;
|
||||
var
|
||||
S: UnicodeString;
|
||||
begin
|
||||
S := aName + '_' + IntToHex(aCRC, 8);
|
||||
Result := MAP_FOLDER_NAME[mkDL] + PathDelim + S + PathDelim + S + aExt;
|
||||
if not FileExists(ExeDir + Result) then
|
||||
Result := MAP_FOLDER_NAME[mkMP] + PathDelim + aName + PathDelim + aName + aExt;
|
||||
end;
|
||||
|
||||
|
||||
function GuessMissionPathRel(const aMissionFileRelSP, aMissionName: string; aMapFullCRC: Cardinal; aIsMultiplayer: Boolean): string;
|
||||
begin
|
||||
if aIsMultiplayer then
|
||||
//In MP we can't store it since it will be MapsMP or MapsDL on different clients
|
||||
Result := GuessMPPathRel(aMissionName, '.dat', aMapFullCRC)
|
||||
else
|
||||
Result := aMissionFileRelSP; //In SP we store it
|
||||
end;
|
||||
|
||||
|
||||
//Try to determine TMapFolder for specified aFolderName
|
||||
//Returns True when succeeded
|
||||
function DetermineMapKind(const aFolderName: UnicodeString; out aMapKind: TKMMapKind): Boolean;
|
||||
var
|
||||
MK: TKMMapKind;
|
||||
begin
|
||||
Result := False;
|
||||
// no need to test mkUnknown
|
||||
for MK := Succ(Low(TKMMapKind)) to High(TKMMapKind) do
|
||||
if aFolderName = MAP_FOLDER_NAME[MK] then
|
||||
begin
|
||||
aMapKind := MK;
|
||||
Exit(True);
|
||||
end;
|
||||
end;
|
||||
|
||||
|
||||
function GetMapKind(aIsMultiplayer: Boolean): TKMMapKind;
|
||||
begin
|
||||
if aIsMultiplayer then
|
||||
Result := mkMP
|
||||
else
|
||||
Result := mkSP;
|
||||
end;
|
||||
|
||||
|
||||
end.
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
unit KM_MapUtilsExt;
|
||||
{$I KaM_Remake.inc}
|
||||
interface
|
||||
uses
|
||||
KM_Defaults, KM_Maps, KM_MapTypes;
|
||||
|
||||
function TryOpenMapPDF(aMap: TKMMapInfo): Boolean;
|
||||
|
||||
function GetGoalDescription(aPlayer1, aPlayer2: TKMHandID; aGoalType: TKMGoalType; aGoalCondition: TKMGoalCondition;
|
||||
aColPlayer1, aColPlayer2, aColTxt, aColBld: Cardinal): string;
|
||||
|
||||
implementation
|
||||
uses
|
||||
SysUtils,
|
||||
KM_Resource, KM_ResTexts, KM_ResHouses, KM_ResTypes,
|
||||
KM_CommonShellUtils, KM_CommonUtils;
|
||||
|
||||
|
||||
function TryOpenMapPDF(aMap: TKMMapInfo): Boolean;
|
||||
var
|
||||
pdfPath: string;
|
||||
begin
|
||||
Result := False;
|
||||
if aMap = nil then Exit;
|
||||
|
||||
pdfPath := aMap.DetermineReadmeFilePath;
|
||||
if pdfPath <> '' then
|
||||
Result := ShellOpenFile(pdfPath);
|
||||
end;
|
||||
|
||||
|
||||
// Format mission goal description
|
||||
function GetGoalDescription(aPlayer1, aPlayer2: TKMHandID; aGoalType: TKMGoalType; aGoalCondition: TKMGoalCondition;
|
||||
aColPlayer1, aColPlayer2, aColTxt, aColBld: Cardinal): string;
|
||||
type
|
||||
TKMGoalTypeDescWordKind = (dwkFirst, dwkSecond);
|
||||
const
|
||||
GOAL_TYPE_DESC_TX: array[TKMGoalType, TKMGoalTypeDescWordKind] of Integer = (
|
||||
(TX_MAPED_GOALS_TYPE_NONE, TX_MAPED_GOALS_TYPE_NONE),
|
||||
(TX_GOAL_CONDITION_DESC_WIN_WORD, TX_GOAL_CONDITION_DESC_DESTROYED_WORD),
|
||||
(TX_GOAL_CONDITION_DESC_SURVIVE_WORD, TX_GOAL_CONDITION_DESC_SAVED_WORD)
|
||||
);
|
||||
|
||||
GOAL_COND_DESC_WORD_TX: array[TKMGoalCondition] of Integer = (
|
||||
TX_MAPED_GOALS_CONDITION_NONE,
|
||||
TX_MAPED_GOALS_CONDITION_TUTORIAL,
|
||||
TX_MAPED_GOALS_CONDITION_TIME,
|
||||
TX_MAPED_GOALS_CONDITION_BUILDS,
|
||||
TX_MAPED_GOALS_CONDITION_TROOPS,
|
||||
TX_MAPED_GOALS_CONDITION_UNKNOWN,
|
||||
TX_MAPED_GOALS_CONDITION_ASSETS,
|
||||
TX_MAPED_GOALS_CONDITION_SERFS,
|
||||
TX_MAPED_GOALS_CONDITION_ECONOMY
|
||||
);
|
||||
|
||||
var
|
||||
I: Integer;
|
||||
goalCondStr, housesStr: string;
|
||||
begin
|
||||
case aGoalCondition of
|
||||
gcBuildings:
|
||||
begin
|
||||
housesStr := '';
|
||||
for I := Low(GOAL_BUILDINGS_HOUSES) to High(GOAL_BUILDINGS_HOUSES) do
|
||||
begin
|
||||
if housesStr <> '' then
|
||||
housesStr := housesStr + ', ';
|
||||
|
||||
housesStr := housesStr + gRes.Houses[GOAL_BUILDINGS_HOUSES[I]].HouseName;
|
||||
end;
|
||||
goalCondStr := Format(gResTexts[TX_GOAL_CONDITION_COND_DESC_BUILDINGS], [WrapWrappedColor(housesStr, aColBld)]);
|
||||
end;
|
||||
else
|
||||
goalCondStr := gResTexts[GOAL_COND_DESC_WORD_TX[aGoalCondition]];
|
||||
end;
|
||||
|
||||
// Format text out of string like:
|
||||
// 'For player %0:s to %1:s player's %2:s %3:s must be %4:s'
|
||||
// We want to get smth like (depends of the locale, ofc)
|
||||
// 'For player 1 to win player's 2 troops must be destroyed'
|
||||
Result := Format(gResTexts[TX_GOAL_CONDITION_DESC_PATTERN], [WrapColor(aPlayer1 + 1, aColPlayer1),
|
||||
WrapColor(gResTexts[GOAL_TYPE_DESC_TX[aGoalType, dwkFirst]], aColTxt),
|
||||
WrapColor(aPlayer2 + 1, aColPlayer2),
|
||||
WrapColor(goalCondStr, aColTxt),
|
||||
WrapColor(gResTexts[GOAL_TYPE_DESC_TX[aGoalType, dwkSecond]], aColTxt)]);
|
||||
end;
|
||||
|
||||
end.
|
||||
+2024
File diff suppressed because it is too large.
Load diff
+330
@@ -0,0 +1,330 @@
|
||||
unit KM_MissionScript;
|
||||
{$I KaM_Remake.inc}
|
||||
interface
|
||||
uses
|
||||
KM_Defaults;
|
||||
|
||||
|
||||
type
|
||||
TKMMissionParsingMode = (
|
||||
mpmSingle,
|
||||
mpmMulti, //Skip players
|
||||
mpmEditor //Ignore errors, load armies differently
|
||||
);
|
||||
|
||||
TKMCommandType = (
|
||||
ctUnknown = 0,
|
||||
ctSetMap,
|
||||
ctSetMaxPlayer,
|
||||
ctSetCurrPlayer,
|
||||
ctHumanPlayer,
|
||||
ctUserPlayer,
|
||||
ctSetHouse,
|
||||
ctSetTactic,
|
||||
ctAIPlayer,
|
||||
ctAdvancedAIPlayer,
|
||||
ctEnablePlayer,
|
||||
ctSetNewRemap,
|
||||
ctSetMapColor,
|
||||
ctSetRGBColor,
|
||||
ctCenterScreen,
|
||||
ctChooseLoc,
|
||||
ctChooseLocAddWare,
|
||||
ctChooseLocAddUnit,
|
||||
ctClearUp,
|
||||
ctBlockTrade,
|
||||
ctBlockUnit,
|
||||
ctBlockHouse,
|
||||
ctReleaseHouse,
|
||||
ctReleaseAllHouses,
|
||||
ctAddGoal,
|
||||
ctAddLostGoal,
|
||||
ctSetUnit,
|
||||
ctSetRoad,
|
||||
ctSetField,
|
||||
ctSetWinefield,
|
||||
ctSetFieldStaged,
|
||||
ctSetWinefieldStaged,
|
||||
ctSetStock,
|
||||
ctAddWare,
|
||||
ctSetAlliance,
|
||||
ctSetHouseDamage,
|
||||
ctSetHouseDeliveryMode,
|
||||
ctSetHouseRepairMode,
|
||||
ctSetHouseClosedForWorker,
|
||||
ctSetUnitByStock,
|
||||
ctUnitAddToLast,
|
||||
ctSetUnitFood,
|
||||
ctSetGroup,
|
||||
ctSetGroupFood,
|
||||
ctSendGroup,
|
||||
ctAttackPosition,
|
||||
ctAddWareToSecond,
|
||||
ctAddWareTo,
|
||||
ctAddWareToLast,
|
||||
ctAddWareToAll,
|
||||
ctAddWeapon,
|
||||
ctAICharacter,
|
||||
ctAINoBuild,
|
||||
ctAIAutoRepair,
|
||||
ctAIAutoAttack,
|
||||
ctAIAutoDefend,
|
||||
ctAIDefendAllies,
|
||||
ctAIUnlimitedEquip,
|
||||
ctAIArmyType,
|
||||
ctAIStartPosition,
|
||||
ctAIDefence,
|
||||
ctAIAttack,
|
||||
ctCopyAIAttack,
|
||||
ctClearAIAttack,
|
||||
ctSetRallyPoint);
|
||||
|
||||
const
|
||||
COMMANDVALUES: array [TKMCommandType] of AnsiString = (
|
||||
'',
|
||||
'SET_MAP',
|
||||
'SET_MAX_PLAYER',
|
||||
'SET_CURR_PLAYER',
|
||||
'SET_HUMAN_PLAYER', //Default human player (name left for compatibility with KaM)
|
||||
'SET_USER_PLAYER', //Player can be human
|
||||
'SET_HOUSE',
|
||||
'SET_TACTIC',
|
||||
'SET_AI_PLAYER', //Player can be AI
|
||||
'SET_ADVANCED_AI_PLAYER', //Player can be Advanced AI
|
||||
'ENABLE_PLAYER',
|
||||
'SET_NEW_REMAP',
|
||||
'SET_MAP_COLOR',
|
||||
'SET_RGB_COLOR',
|
||||
'CENTER_SCREEN',
|
||||
'CHOOSE_LOC',
|
||||
'CHOOSE_LOC_ADD_WARE',
|
||||
'CHOOSE_LOC_ADD_UNIT',
|
||||
'CLEAR_UP',
|
||||
'BLOCK_TRADE',
|
||||
'BLOCK_UNIT',
|
||||
'BLOCK_HOUSE',
|
||||
'RELEASE_HOUSE',
|
||||
'RELEASE_ALL_HOUSES',
|
||||
'ADD_GOAL',
|
||||
'ADD_LOST_GOAL',
|
||||
'SET_UNIT',
|
||||
'SET_STREET',
|
||||
'SET_FIELD',
|
||||
'SET_WINEFIELD',
|
||||
'SET_FIELD_STAGED',
|
||||
'SET_WINEFIELD_STAGED',
|
||||
'SET_STOCK','ADD_WARE',
|
||||
'SET_ALLIANCE',
|
||||
'SET_HOUSE_DAMAGE',
|
||||
'SET_HOUSE_DELIVERY_MODE',
|
||||
'SET_HOUSE_REPAIR_MODE',
|
||||
'SET_HOUSE_CLOSED_FOR_WORKER',
|
||||
'SET_UNIT_BY_STOCK',
|
||||
'ADD_UNIT_TO_LAST',
|
||||
'SET_UNIT_FOOD',
|
||||
'SET_GROUP',
|
||||
'SET_GROUP_FOOD',
|
||||
'SEND_GROUP',
|
||||
'ATTACK_POSITION',
|
||||
'ADD_WARE_TO_SECOND',
|
||||
'ADD_WARE_TO',
|
||||
'ADD_WARE_TO_LAST',
|
||||
'ADD_WARE_TO_ALL',
|
||||
'ADD_WEAPON',
|
||||
'SET_AI_CHARACTER',
|
||||
'SET_AI_NO_BUILD',
|
||||
'SET_AI_AUTO_REPAIR',
|
||||
'SET_AI_AUTO_ATTACK',
|
||||
'SET_AI_AUTO_DEFEND',
|
||||
'SET_AI_DEFEND_ALLIES',
|
||||
'SET_AI_UNLIMITED_EQUIP',
|
||||
'SET_AI_ARMY_TYPE',
|
||||
'SET_AI_START_POSITION',
|
||||
'SET_AI_DEFENSE',
|
||||
'SET_AI_ATTACK',
|
||||
'COPY_AI_ATTACK',
|
||||
'CLEAR_AI_ATTACK',
|
||||
'SET_RALLY_POINT');
|
||||
|
||||
type
|
||||
TKMMissionParserCommon = class
|
||||
protected
|
||||
fMissionFileName: string;
|
||||
fLastHand: TKMHandID; //Current Player
|
||||
|
||||
// Fatal errors generate exceptions
|
||||
// Minor error descriptions accumulate here
|
||||
fMinorErrors: string;
|
||||
|
||||
function TextToCommandType(const aCommandText: AnsiString): TKMCommandType;
|
||||
function ReadMissionFile(const aFileName: string): AnsiString;
|
||||
procedure TokenizeScript(const aText: AnsiString; aMaxCmd: Byte; aCommands: array of AnsiString);
|
||||
procedure ProcessCommand(CommandType: TKMCommandType; P: array of Integer; const TextParam: AnsiString = ''); virtual; abstract;
|
||||
procedure AddError(const aErrorMsg: string);
|
||||
public
|
||||
property MinorErrors: string read fMinorErrors;
|
||||
procedure LoadMission(const aFileName: string); virtual;
|
||||
end;
|
||||
|
||||
|
||||
implementation
|
||||
uses
|
||||
Classes, SysUtils;
|
||||
|
||||
|
||||
{ TKMMissionParserCommon }
|
||||
procedure TKMMissionParserCommon.LoadMission(const aFileName: string);
|
||||
begin
|
||||
fMissionFileName := aFileName;
|
||||
fLastHand := -1;
|
||||
end;
|
||||
|
||||
|
||||
function TKMMissionParserCommon.TextToCommandType(const aCommandText: AnsiString): TKMCommandType;
|
||||
var
|
||||
ct: TKMCommandType;
|
||||
begin
|
||||
Result := ctUnknown;
|
||||
|
||||
for ct := Low(TKMCommandType) to High(TKMCommandType) do
|
||||
if aCommandText = '!' + COMMANDVALUES[ct] then
|
||||
Exit(ct);
|
||||
end;
|
||||
|
||||
|
||||
// Read mission file to a string and if necessary - decode it
|
||||
function TKMMissionParserCommon.ReadMissionFile(const aFileName: string): AnsiString;
|
||||
var
|
||||
I, num: Cardinal;
|
||||
F: TMemoryStream;
|
||||
begin
|
||||
if not FileExists(aFileName) then
|
||||
raise Exception.Create(Format('Mission file %s could not be found', [aFileName]));
|
||||
|
||||
// Load and decode .DAT file into FileText
|
||||
F := TMemoryStream.Create;
|
||||
try
|
||||
F.LoadFromFile(aFileName);
|
||||
|
||||
if F.Size = 0 then
|
||||
raise Exception.Create(Format('Mission file %s is empty', [aFileName]));
|
||||
|
||||
// Detect whether mission is encoded so we can support decoded/encoded .DAT files
|
||||
// We can't test 1st char, it can be any. Instead see how often common chracters meet
|
||||
num := 0;
|
||||
for I := 0 to F.Size - 1 do //tab, eol, 0..9, space, !
|
||||
if PByte(NativeUInt(F.Memory)+I)^ in [9,10,13,ord('0')..ord('9'),$20,$21] then
|
||||
inc(num);
|
||||
|
||||
//Usually 30-50% is numerals/spaces, tested on typical KaM maps, take half of that as margin
|
||||
if (num/F.Size < 0.20) then
|
||||
for I := 0 to F.Size - 1 do
|
||||
PByte(NativeUInt(F.Memory)+I)^ := PByte(NativeUInt(F.Memory)+I)^ xor 239;
|
||||
|
||||
//Save text after decoding but before cleaning
|
||||
if WRITE_DECODED_MISSION then
|
||||
F.SaveToFile(aFileName + '.txt');
|
||||
|
||||
for I := 0 to F.Size - 1 do
|
||||
if PByte(NativeUInt(F.Memory)+I)^ in [9, 10, 13] then //tab, eol
|
||||
PByte(NativeUInt(F.Memory)+I)^ := $20; //Space
|
||||
|
||||
num := 0;
|
||||
for I := 0 to F.Size - 1 do
|
||||
begin
|
||||
PByte(NativeUInt(F.Memory)+num)^ := PByte(NativeUInt(F.Memory)+I)^;
|
||||
if (num <= 0)
|
||||
or (
|
||||
(PWord(NativeUInt(F.Memory) + num-1)^ <> $2020) //Skip double spaces and !!
|
||||
and (PWord(NativeUInt(F.Memory) + num-1)^ <> $2121)) then
|
||||
Inc(num);
|
||||
end;
|
||||
|
||||
SetLength(Result, num); //Because some extra characters were removed
|
||||
F.Position := 0;
|
||||
F.ReadBuffer(Result[1], num);
|
||||
finally
|
||||
F.Free;
|
||||
end;
|
||||
//FileText should now be formatted nicely with 1 space between each parameter/command
|
||||
end;
|
||||
|
||||
|
||||
procedure TKMMissionParserCommon.TokenizeScript(const aText: AnsiString; aMaxCmd: Byte; aCommands: array of AnsiString);
|
||||
var
|
||||
I, J, K, intParam: Integer;
|
||||
commandText, strParam, textParam: AnsiString;
|
||||
paramList: array of Integer;
|
||||
commandType: TKMCommandType;
|
||||
doProcess: Boolean;
|
||||
begin
|
||||
SetLength(paramList, aMaxCmd);
|
||||
|
||||
I := 1;
|
||||
repeat
|
||||
if aText[I] = '!' then
|
||||
begin
|
||||
// Extract command until a space
|
||||
commandText := '';
|
||||
repeat
|
||||
commandText := commandText + aText[I];
|
||||
Inc(I);
|
||||
until((aText[I] = #32) or (I >= Length(aText)));
|
||||
|
||||
// We can skip certain commands to speed up the scan
|
||||
// for implementations that need only Preview/Info
|
||||
doProcess := Length(aCommands) = 0;
|
||||
for J := Low(aCommands) to High(aCommands) do
|
||||
if (commandText = aCommands[J]) then
|
||||
doProcess := True;
|
||||
|
||||
if doProcess then
|
||||
begin
|
||||
// Default uninitialized values
|
||||
textParam := '';
|
||||
for K := 0 to aMaxCmd - 1 do
|
||||
paramList[K] := -1;
|
||||
|
||||
// Now convert command into type
|
||||
commandType := TextToCommandType(commandText);
|
||||
Inc(I);
|
||||
// Extract parameters
|
||||
for K := 0 to aMaxCmd - 1 do
|
||||
if (I < Length(aText)) and (aText[I] <> '!') then
|
||||
begin
|
||||
strParam := '';
|
||||
repeat
|
||||
strParam := strParam + aText[I];
|
||||
Inc(I);
|
||||
until((I >= Length(aText)) or (aText[I] = '!') or (aText[I] = #32)); // Until we find another ! OR we run out of data
|
||||
|
||||
// Convert to an integer, if possible
|
||||
if TryStrToInt(string(strParam), intParam) then
|
||||
paramList[K] := intParam
|
||||
else
|
||||
if K = 0 then
|
||||
textParam := strParam; // Accept text for first parameter
|
||||
|
||||
if (I <= Length(aText)) and (aText[I] = #32) then
|
||||
Inc(I);
|
||||
end;
|
||||
|
||||
// We now have command text and parameters, so process them
|
||||
ProcessCommand(commandType, paramList, textParam);
|
||||
end;
|
||||
end
|
||||
else
|
||||
Inc(I);
|
||||
until (I >= Length(aText));
|
||||
end;
|
||||
|
||||
|
||||
// A nice way of debugging script errors.
|
||||
// Shows the error to the user so they know exactly what they did wrong.
|
||||
procedure TKMMissionParserCommon.AddError(const aErrorMsg: string);
|
||||
begin
|
||||
fMinorErrors := fMinorErrors + aErrorMsg + '|';
|
||||
end;
|
||||
|
||||
|
||||
end.
|
||||
@@ -0,0 +1,149 @@
|
||||
unit KM_MissionScript_Info;
|
||||
{$I KaM_Remake.inc}
|
||||
interface
|
||||
uses
|
||||
KM_MissionScript, KM_Maps;
|
||||
|
||||
|
||||
type
|
||||
TKMMissionParsing = (
|
||||
pmBase, // Load base map info for SP maplist (player count, tactic, description)
|
||||
pmExtra, // Load extra map info to be displayed when map is selected (goals, alliances, etc)
|
||||
pmPlayers // Load information only about players (count, allowed types)
|
||||
);
|
||||
|
||||
|
||||
TKMMissionParserInfo = class(TKMMissionParserCommon)
|
||||
private
|
||||
fMapInfo: TKMMapInfo; // We are given this structure and asked to fill it
|
||||
procedure LoadMapInfo(const aFileName: string);
|
||||
protected
|
||||
procedure ProcessCommand(CommandType: TKMCommandType; P: array of Integer; const TextParam: AnsiString = ''); override;
|
||||
public
|
||||
procedure LoadMission(const aFileName: string; aMapInfo: TKMMapInfo; aParsing: TKMMissionParsing); reintroduce;
|
||||
end;
|
||||
|
||||
|
||||
implementation
|
||||
uses
|
||||
SysUtils, Math,
|
||||
KM_Resource,
|
||||
KM_CommonClasses, KM_Defaults, KM_Utils, KM_MapTypes;
|
||||
|
||||
|
||||
{ TKMMissionParserInfo }
|
||||
procedure TKMMissionParserInfo.LoadMission(const aFileName: string; aMapInfo: TKMMapInfo; aParsing: TKMMissionParsing);
|
||||
const
|
||||
COMMANDS_BASE: array [0..3] of AnsiString = (
|
||||
'!SET_MAX_PLAYER', '!SET_TACTIC', '!SET_CURR_PLAYER', '!SET_USER_PLAYER');
|
||||
COMMANDS_EXTRA: array [0..11] of AnsiString = (
|
||||
'!SET_MAX_PLAYER', '!SET_TACTIC',
|
||||
'!SET_CURR_PLAYER', '!SET_HUMAN_PLAYER', '!SET_USER_PLAYER',
|
||||
'!SET_AI_PLAYER', '!SET_ADVANCED_AI_PLAYER', '!ADD_GOAL', '!ADD_LOST_GOAL', '!SET_ALLIANCE', '!SET_MAP_COLOR', '!SET_RGB_COLOR');
|
||||
COMMANDS_PLAYERS: array [0..5] of AnsiString = (
|
||||
'!SET_MAX_PLAYER', '!SET_CURR_PLAYER', '!SET_USER_PLAYER', '!SET_HUMAN_PLAYER', '!SET_AI_PLAYER', '!SET_ADVANCED_AI_PLAYER');
|
||||
var
|
||||
fileText: AnsiString;
|
||||
begin
|
||||
fMapInfo := aMapInfo;
|
||||
|
||||
inherited LoadMission(aFileName);
|
||||
|
||||
fileText := ReadMissionFile(aFileName);
|
||||
if fileText = '' then
|
||||
raise Exception.Create('Script is empty');
|
||||
|
||||
// For info we need only few commands, it makes sense to skip the rest
|
||||
case aParsing of
|
||||
pmBase: TokenizeScript(fileText, 4, COMMANDS_BASE);
|
||||
pmExtra: TokenizeScript(fileText, 4, COMMANDS_EXTRA);
|
||||
pmPlayers: TokenizeScript(fileText, 4, COMMANDS_PLAYERS);
|
||||
end;
|
||||
|
||||
LoadMapInfo(ChangeFileExt(fMissionFileName, '.map'));
|
||||
end;
|
||||
|
||||
|
||||
procedure TKMMissionParserInfo.ProcessCommand(CommandType: TKMCommandType; P: array of Integer; const TextParam: AnsiString = '');
|
||||
begin
|
||||
case CommandType of
|
||||
ctSetMaxPlayer: fMapInfo.LocCount := P[0];
|
||||
|
||||
ctSetTactic: fMapInfo.MissionMode := mmFighting;
|
||||
|
||||
ctSetCurrPlayer: fLastHand := P[0];
|
||||
|
||||
ctHumanPlayer: begin
|
||||
//Default human player can be human, obviously
|
||||
fMapInfo.DefaultHuman := P[0];
|
||||
fMapInfo.CanBeHuman[P[0]] := True;
|
||||
end;
|
||||
|
||||
ctUserPlayer: if P[0] = -1 then
|
||||
fMapInfo.CanBeHuman[fLastHand] := True
|
||||
else
|
||||
fMapInfo.CanBeHuman[P[0]] := True;
|
||||
|
||||
ctAIPlayer: if P[0] = -1 then
|
||||
fMapInfo.CanBeClassicAI[fLastHand] := True
|
||||
else
|
||||
fMapInfo.CanBeClassicAI[P[0]] := True;
|
||||
|
||||
ctAdvancedAIPlayer:if P[0] = -1 then
|
||||
fMapInfo.CanBeAdvancedAI[fLastHand] := True
|
||||
else
|
||||
fMapInfo.CanBeAdvancedAI[P[0]] := True;
|
||||
|
||||
ctAddGoal: if fLastHand >= 0 then
|
||||
//If the condition is time then P[3] is the time, else it is player ID
|
||||
if TKMGoalCondition(P[0]) = gcTime then
|
||||
fMapInfo.AddGoal(gltVictory, fLastHand, TKMGoalCondition(P[0]), TKMGoalStatus(P[1]), -1)
|
||||
else
|
||||
fMapInfo.AddGoal(gltVictory, fLastHand, TKMGoalCondition(P[0]), TKMGoalStatus(P[1]), P[3]);
|
||||
|
||||
ctAddLostGoal: if fLastHand >= 0 then
|
||||
//If the condition is time then P[3] is the time, else it is player ID
|
||||
if TKMGoalCondition(P[0]) = gcTime then
|
||||
fMapInfo.AddGoal(gltSurvive, fLastHand, TKMGoalCondition(P[0]), TKMGoalStatus(P[1]), -1)
|
||||
else
|
||||
fMapInfo.AddGoal(gltSurvive, fLastHand, TKMGoalCondition(P[0]), TKMGoalStatus(P[1]), P[3]);
|
||||
|
||||
ctSetAlliance: if (fLastHand >= 0) and (P[0] <> fLastHand) then //Can't be enemies with yourself
|
||||
if P[1] = 1 then
|
||||
fMapInfo.Alliances[fLastHand, P[0]] := atAlly
|
||||
else
|
||||
fMapInfo.Alliances[fLastHand, P[0]] := atEnemy;
|
||||
|
||||
ctSetMapColor: if fLastHand >= 0 then
|
||||
//For now simply use the minimap color for all color, it is too hard to load all 8 shades from ctSetNewRemap
|
||||
fMapInfo.FlagColors[fLastHand] := gRes.Palettes.DefaultPalette.Color32(P[0]);
|
||||
|
||||
ctSetRGBColor: if fLastHand >= 0 then
|
||||
fMapInfo.FlagColors[fLastHand] := P[0] or $FF000000;
|
||||
end;
|
||||
end;
|
||||
|
||||
|
||||
// Acquire essential terrain details
|
||||
procedure TKMMissionParserInfo.LoadMapInfo(const aFileName: string);
|
||||
var
|
||||
S: TKMemoryStream;
|
||||
newX, newY: Integer;
|
||||
begin
|
||||
if not FileExists(aFileName) then
|
||||
raise Exception.Create('Map file couldn''t be found');
|
||||
|
||||
S := TKMemoryStreamBinary.Create;
|
||||
try
|
||||
S.LoadFromFile(aFileName);
|
||||
LoadMapHeader(S, newX, newY);
|
||||
finally
|
||||
S.Free;
|
||||
end;
|
||||
|
||||
fMapInfo.MapSizeX := newX;
|
||||
fMapInfo.MapSizeY := newY;
|
||||
end;
|
||||
|
||||
|
||||
end.
|
||||
@@ -0,0 +1,266 @@
|
||||
unit KM_MissionScript_Preview;
|
||||
{$I KaM_Remake.inc}
|
||||
interface
|
||||
uses
|
||||
KM_MissionScript,
|
||||
KM_Defaults, KM_Points;
|
||||
|
||||
|
||||
type
|
||||
TKMTilePreview = record
|
||||
TileID: Word;
|
||||
TileHeight: Byte; //Used for calculating light
|
||||
TileOwner: TKMHandID;
|
||||
Revealed: Boolean;
|
||||
end;
|
||||
|
||||
TKMHandPreview = record
|
||||
Color: Cardinal;
|
||||
StartingLoc: TKMPoint;
|
||||
CanHuman, CanAI: Boolean;
|
||||
end;
|
||||
|
||||
//Specially optimized mission parser for map previews
|
||||
TKMMissionParserPreview = class(TKMMissionParserCommon)
|
||||
private
|
||||
fMapX: Integer;
|
||||
fMapY: Integer;
|
||||
fHandPreview: array [0 .. MAX_HANDS-1] of TKMHandPreview;
|
||||
fMapPreview: array of TKMTilePreview;
|
||||
|
||||
fRevealFor: array of TKMHandID;
|
||||
|
||||
function GetTileInfo(X, Y: Integer): TKMTilePreview;
|
||||
function GetPlayerInfo(aIndex: Byte): TKMHandPreview;
|
||||
procedure LoadMapData(const aFileName: string);
|
||||
protected
|
||||
procedure ProcessCommand(CommandType: TKMCommandType; P: array of Integer; const TextParam: AnsiString = ''); override;
|
||||
public
|
||||
property MapPreview[X, Y: Integer]: TKMTilePreview read GetTileInfo;
|
||||
property PlayerPreview[aIndex: Byte]: TKMHandPreview read GetPlayerInfo;
|
||||
property MapX: Integer read fMapX;
|
||||
property MapY: Integer read fMapY;
|
||||
procedure LoadMission(const aFileName: string; const aRevealFor: array of TKMHandID); reintroduce;
|
||||
end;
|
||||
|
||||
|
||||
implementation
|
||||
uses
|
||||
Classes, SysUtils, Math,
|
||||
KM_TerrainUtils,
|
||||
KM_HandTypes,
|
||||
KM_Resource, KM_ResHouses, KM_ResUnits,
|
||||
KM_CommonClasses, KM_CommonUtils, KM_Utils,
|
||||
KM_ResTypes, KM_TerrainTypes;
|
||||
|
||||
|
||||
{ TKMMissionParserPreview }
|
||||
function TKMMissionParserPreview.GetTileInfo(X,Y: Integer): TKMTilePreview;
|
||||
begin
|
||||
Result := fMapPreview[(Y-1)*fMapX + X-1];
|
||||
end;
|
||||
|
||||
|
||||
function TKMMissionParserPreview.GetPlayerInfo(aIndex: Byte): TKMHandPreview;
|
||||
begin
|
||||
Result := fHandPreview[aIndex];
|
||||
end;
|
||||
|
||||
|
||||
// Load terrain data into liteweight structure, take only what we need for preview
|
||||
procedure TKMMissionParserPreview.LoadMapData(const aFileName: string);
|
||||
var
|
||||
I: Integer;
|
||||
S: TKMemoryStream;
|
||||
tileBasic: TKMTerrainTileBasic;
|
||||
gameRev: Integer;
|
||||
begin
|
||||
if not FileExists(aFileName) then
|
||||
raise Exception.Create('Map file couldn''t be found');
|
||||
|
||||
S := TKMemoryStreamBinary.Create;
|
||||
try
|
||||
gameRev := 0;
|
||||
S.LoadFromFile(aFileName);
|
||||
|
||||
LoadMapHeader(S, fMapX, fMapY, gameRev);
|
||||
|
||||
SetLength(fMapPreview, fMapX * fMapY);
|
||||
for I := 0 to fMapX * fMapY - 1 do
|
||||
begin
|
||||
ReadTileFromStream(S, tileBasic, gameRev);
|
||||
fMapPreview[I].TileID := tileBasic.BaseLayer.Terrain;
|
||||
fMapPreview[I].TileHeight := tileBasic.Height;
|
||||
|
||||
// Fill in blanks
|
||||
fMapPreview[I].TileOwner := HAND_NONE;
|
||||
fMapPreview[I].Revealed := False;
|
||||
end;
|
||||
finally
|
||||
S.Free;
|
||||
end;
|
||||
end;
|
||||
|
||||
|
||||
procedure TKMMissionParserPreview.ProcessCommand(CommandType: TKMCommandType; P: array of Integer; const TextParam: AnsiString = '');
|
||||
|
||||
function PointInMap(X, Y: Integer): Boolean;
|
||||
begin
|
||||
Result := InRange(X, 1, fMapX)
|
||||
and InRange(Y, 1, fMapY);
|
||||
end;
|
||||
|
||||
procedure SetOwner(X,Y: Word);
|
||||
begin
|
||||
fMapPreview[X-1 + (Y-1)*fMapX].TileOwner := fLastHand;
|
||||
end;
|
||||
|
||||
function RevealForPlayer(aPlayerIndex: TKMHandID): Boolean;
|
||||
var
|
||||
I: Integer;
|
||||
begin
|
||||
Result := False;
|
||||
for I := 0 to Length(fRevealFor)-1 do
|
||||
if (fRevealFor[I] = aPlayerIndex) then
|
||||
Exit(True);
|
||||
end;
|
||||
|
||||
procedure RevealCircle(X,Y,Radius: Word);
|
||||
var
|
||||
I, K: Word;
|
||||
begin
|
||||
if not RevealForPlayer(fLastHand) then
|
||||
Exit;
|
||||
// Unknown which overload to take
|
||||
for I := Max(Integer(Y-Radius),1) to Min(Integer(Y+Radius),Integer(fMapY)) do
|
||||
for K := Max(Integer(X-Radius),1) to Min(Integer(X+Radius),Integer(fMapX)) do
|
||||
if (Sqr(X-K) + Sqr(Y-I)) <= Sqr(Radius) then
|
||||
fMapPreview[(I-1)*fMapX + K-1].Revealed := True;
|
||||
end;
|
||||
|
||||
var
|
||||
I, K: Integer;
|
||||
HA: TKMHouseArea;
|
||||
valid: Boolean;
|
||||
loc: TKMPoint;
|
||||
begin
|
||||
case CommandType of
|
||||
ctSetCurrPlayer: fLastHand := P[0];
|
||||
|
||||
ctSetHouse: if InRange(P[0], Low(HOUSE_ID_TO_TYPE), High(HOUSE_ID_TO_TYPE))
|
||||
and PointInMap(P[1]+1, P[2]+1) then
|
||||
begin
|
||||
RevealCircle(P[1]+1, P[2]+1, gRes.Houses[HOUSE_ID_TO_TYPE[P[0]]].Sight);
|
||||
HA := gRes.Houses[HOUSE_ID_TO_TYPE[P[0]]].BuildArea;
|
||||
for i:=1 to 4 do for k:=1 to 4 do
|
||||
if HA[i,k] <> 0 then
|
||||
if InRange(P[1]+1+k-3, 1, fMapX) and InRange(P[2]+1+i-4, 1, fMapY) then
|
||||
SetOwner(P[1]+1+k-3, P[2]+1+i-4);
|
||||
end;
|
||||
|
||||
ctSetMapColor: if InRange(fLastHand, 0, MAX_HANDS-1) then
|
||||
fHandPreview[fLastHand].Color := gRes.Palettes.DefaultPalette.Color32(P[0]);
|
||||
|
||||
ctSetRGBColor: if InRange(fLastHand, 0, MAX_HANDS-1) then
|
||||
fHandPreview[fLastHand].Color := P[0] or $FF000000;
|
||||
|
||||
ctCenterScreen: if PointInMap(P[0]+1, P[1]+1) then
|
||||
fHandPreview[fLastHand].StartingLoc := TKMPoint.New(P[0]+1,P[1]+1);
|
||||
|
||||
ctHumanPlayer: //Default human player can be human, obviously
|
||||
fHandPreview[P[0]].CanHuman := True;
|
||||
|
||||
ctUserPlayer: if P[0] = -1 then
|
||||
fHandPreview[fLastHand].CanHuman := True
|
||||
else
|
||||
fHandPreview[P[0]].CanHuman := True;
|
||||
|
||||
ctAIPlayer,
|
||||
ctAdvancedAIPlayer:if P[0] = -1 then
|
||||
fHandPreview[fLastHand].CanAI := True
|
||||
else
|
||||
fHandPreview[P[0]].CanAI := True;
|
||||
ctSetRoad,
|
||||
ctSetField,
|
||||
ctSetWinefield,
|
||||
ctSetFieldStaged,
|
||||
ctSetWinefieldStaged:
|
||||
if PointInMap(P[0]+1, P[1]+1) then
|
||||
SetOwner(P[0]+1, P[1]+1);
|
||||
|
||||
ctSetUnit: if PointInMap(P[1]+1, P[2]+1) and
|
||||
not (UNIT_OLD_ID_TO_TYPE[P[0]] in [ANIMAL_MIN..ANIMAL_MAX]) then //Skip animals
|
||||
begin
|
||||
SetOwner(P[1]+1, P[2]+1);
|
||||
RevealCircle(P[1]+1, P[2]+1, gRes.Units[UNIT_OLD_ID_TO_TYPE[P[0]]].Sight);
|
||||
end;
|
||||
|
||||
ctSetStock: if PointInMap(P[1]+1, P[2]+1) then
|
||||
begin
|
||||
//Set Store and roads below
|
||||
ProcessCommand(ctSetHouse,[11,P[0]+1,P[1]+1]);
|
||||
ProcessCommand(ctSetRoad, [ P[0]-2,P[1]+1]);
|
||||
ProcessCommand(ctSetRoad, [ P[0]-1,P[1]+1]);
|
||||
ProcessCommand(ctSetRoad, [ P[0] ,P[1]+1]);
|
||||
end;
|
||||
|
||||
ctSetGroup: if InRange(P[0], Low(UNIT_ID_TO_TYPE), High(UNIT_ID_TO_TYPE)) and (UNIT_ID_TO_TYPE[P[0]] <> utNone)
|
||||
and PointInMap(P[1]+1, P[2]+1) then
|
||||
for I := 0 to P[5] - 1 do
|
||||
begin
|
||||
loc := GetPositionInGroup2(P[1]+1,P[2]+1,TKMDirection(P[3]+1), I, P[4],fMapX,fMapY,valid);
|
||||
if valid then
|
||||
begin
|
||||
SetOwner(loc.X,loc.Y);
|
||||
RevealCircle(P[1]+1, P[2]+1, gRes.Units[UNIT_OLD_ID_TO_TYPE[P[0]]].Sight);
|
||||
end;
|
||||
end;
|
||||
|
||||
ctClearUp: begin
|
||||
if (P[0] = 255) then
|
||||
begin
|
||||
if RevealForPlayer(fLastHand) then
|
||||
for I := 0 to fMapX * fMapY - 1 do
|
||||
fMapPreview[I].Revealed := True;
|
||||
end
|
||||
else if PointInMap(P[0]+1, P[1]+1) then
|
||||
RevealCircle(P[0]+1, P[1]+1, P[2]);
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
|
||||
|
||||
// We use custom mission loader for speed (compare only used commands)
|
||||
procedure TKMMissionParserPreview.LoadMission(const aFileName: string; const aRevealFor: array of TKMHandID);
|
||||
const
|
||||
ALLOWED_COMMANDS: array [0..16] of AnsiString = (
|
||||
'!SET_MAP', '!SET_MAP_COLOR', '!SET_RGB_COLOR', '!SET_AI_PLAYER', '!SET_ADVANCED_AI_PLAYER', '!CENTER_SCREEN',
|
||||
'!SET_CURR_PLAYER', '!SET_HUMAN_PLAYER', '!SET_USER_PLAYER',
|
||||
'!SET_STREET', '!SET_FIELD', '!SET_WINEFIELD', '!SET_STOCK',
|
||||
'!SET_HOUSE', '!CLEAR_UP', '!SET_UNIT', '!SET_GROUP');
|
||||
var
|
||||
I: Integer;
|
||||
fileText: AnsiString;
|
||||
begin
|
||||
inherited LoadMission(aFileName);
|
||||
|
||||
SetLength(fRevealFor, Length(aRevealFor));
|
||||
for I := Low(aRevealFor) to High(aRevealFor) do
|
||||
fRevealFor[I] := aRevealFor[I];
|
||||
|
||||
FillChar(fHandPreview, SizeOf(fHandPreview), #0);
|
||||
for I := 0 to MAX_HANDS-1 do
|
||||
fHandPreview[I].Color := DEFAULT_PLAYERS_COLORS[I];
|
||||
|
||||
fileText := ReadMissionFile(aFileName);
|
||||
if fileText = '' then
|
||||
raise Exception.Create('Script is empty');
|
||||
|
||||
// We need to load map dimensions first, so that SetGroup could access map bounds
|
||||
LoadMapData(ChangeFileExt(fMissionFileName, '.map'));
|
||||
|
||||
TokenizeScript(fileText, 6, ALLOWED_COMMANDS);
|
||||
end;
|
||||
|
||||
|
||||
end.
|
||||
File diff suppressed because it is too large.
Load diff
Reference in new issue
Block a user