// DOCUMENTATION // // The technology tree file is parsed into memory in one pass. // // The file consists of a list of three kinds of blocks: // // Breakthrough // News // Technology // Topic // // The {$MODE OBJFPC} { -*- delphi -*- } {$INCLUDE settings.inc} unit techtree; {$IFDEF DEBUG} //{$DEFINE DEBUG_TECHTREE_PARSER} {$ENDIF} // XXX "Needs:" should support negative requirements to block off a branch of the tech tree // XXX (rather than having to use a very high negative bonus as now) // We need there to always be at least one topic you can research // XXX should make research features something you earn with Election // XXX then we wouldn't need a filler entry here // XXX news should also be available for when topics and technologies become available // XXX that way you can have something depend on a group of other things, and the news appears // as soon as it is available, without having to screw around with a placeholder breakthrough // (though... maybe a breakthrough is ok since that way it delays the memos a bit...) interface uses storable, hashtable, hashfunctions, stringutils, rpc, autostorable, stringstream, typinfo; type TNewsNode = record Name, Kind, Body: UTF8String; end; PNewsArray = ^TNewsArray; TNewsArray = array of TNewsNode; PTechnologyTreeNode = ^TTechnologyTreeNode; TTechnologyTreeNode = record public type TBreakthroughBonus = record PrerequisiteNode: PTechnologyTreeNode; // tkBreakthrough or tkTopic Bonus: Integer; end; PBreakthroughBonusArray = ^TBreakthroughBonusArray; TBreakthroughBonusArray = array of TBreakthroughBonus; TFacilityDescription = record Facility: TAbstractFacilityClass; FacilitySettings: TAutoStorable; end; PFacilityDescriptionArray = ^TFacilityDescriptionArray; TFacilityDescriptionArray = array of TFacilityDescription; var Name: UTF8String; ID: TTechnologyTreeNodeID; DependencyCount: TDependencyCount; PrerequisiteFor: array of PTechnologyTreeNode; case Kind: TTechnologyTreeNodeKind of tkBreakthrough: ( Difficulty: Integer; // seconds at a bog-standard facility MinimumDifficulty: Cardinal; // seconds at a bog-standard facility DiscoveryLambda: Double; // times this could be discovered per second at a bog-standard facility Bonuses: PBreakthroughBonusArray; News: PNewsArray); tkTopic: (); tkTechnology: ( Actor: TAbstractActorClass; ActorSettings: TAutoStorable; Facilities: PFacilityDescriptionArray); end; TTechnologyTreeNodeHashTable = class(specialize THashTable ) public constructor Create(PredictedCount: THashTableSizeInt = 8); end; PTechnologyTree = ^TTechnologyTree; TTechnologyTree = record Nodes: array of PTechnologyTreeNode; NameIndex: TTechnologyTreeNodeHashTable; end; TTechnologyTreeManager = class(TAbstractTechnologyTreeManager) // @RegisterStorableClass protected type PParsedTechTreeBonus = ^TParsedTechTreeBonus; TParsedTechTreeBonus = record Next: PParsedTechTreeBonus; Name: UTF8String; Bonus: Integer; Kind: TTechnologyTreeNodeKind; end; TParsedTechTreeBlockType = (ptNone = Ord(tkNone), ptBreakthrough = Ord(tkBreakthrough), ptTopic = Ord(tkTopic), ptTechnology = Ord(tkTechnology), ptNews); const ptTechTreeBlockTypes: set of TParsedTechTreeBlockType = [ptBreakthrough, ptTopic, ptTechnology]; type PParsedFacilityDescriptions = ^TParsedFacilityDescriptions; TParsedFacilityDescriptions = record Next: PParsedFacilityDescriptions; Facility: TAbstractFacilityClass; FacilitySettings: TAutoStorable; // set to nil once copied elsewhere end; PParsedTechTreeBlock = ^TParsedTechTreeBlock; TParsedTechTreeBlock = record Next: PParsedTechTreeBlock; Name: UTF8String; Needs: array of UTF8String; case BlockType: TParsedTechTreeBlockType of ptBreakthrough: ( Difficulty: Integer; HadDifficulty: Boolean; MinimumDifficulty: Cardinal; HadMinimumDifficulty: Boolean; Obscurity: Cardinal; HadObscurity: Boolean; BonusLines: PParsedTechTreeBonus); ptTopic: (); ptTechnology: ( Actor: TAbstractActorClass; ActorSettings: TAutoStorable; // set to nil once copied elsewhere Facilities: PParsedFacilityDescriptions); ptNews: ( NewsKind: ^UTF8String; NewsBody: ^UTF8String; NewsFor: ^UTF8String); end; var FFilename: UTF8String; FTechnologyTree: PTechnologyTree; procedure FreeParsedTechTree(var Victim: PParsedTechTreeBlock); procedure FreeCompiledTechTree(var Tree: PTechnologyTree); function Parse(out ErrorMessage: TErrorString): PParsedTechTreeBlock; function Compile(var ParsedTechTree: PParsedTechTreeBlock; out ErrorMessage: TErrorString): PTechnologyTree; function Update(NewTree: PTechnologyTree; out ErrorMessage: TErrorString): Boolean; public constructor Create(Filename: UTF8String); destructor Destroy(); override; constructor Read(Stream: TReadStream); override; procedure Write(Stream: TWriteStream); override; function GetFreshTechTreeStatusArray(): TTechnologyTreeNodeStatusArray; override; procedure FilterTechTree(Kinds: TTechnologyTreeNodeKindSet; Callback: TTechnologyTreeFilterProc); override; function GetIDFor(Name: UTF8String; Kinds: TTechnologyTreeNodeKindSet): TTechnologyTreeNodeIDOrNone; override; function GetPointsForBreakthrough(ID: TTechnologyTreeNodeID; const Status: TTechnologyTreeNodeStatusArray; Topic: TTechnologyTreeNodeIDOrNone; TimeStudiedAtStart, Interval: TDateTime; out Lambda: Double): Double; override; function Discover(ID: TTechnologyTreeNodeID; var Status: TTechnologyTreeNodeStatusArray; Dynasty: TAbstractDynasty): TTechnologyTreeNodeKindSet; override; procedure DescribeTechnologies(Technologies: TTechnologyTreeNodeArray; Stream: TStringStreamWriter); override; function Build(ID: TTechnologyTreeNodeID; Dynasty: TAbstractDynasty): TAbstractActor; override; end; implementation {$IFDEF DEBUG_TECHTREE_PARSER} {$HINT Output of techtree parser will be verbose; disable DEBUG_TECHTREE_PARSER to reduce verbosity} {$IFNDEF DEBUG} {$FATAL DEBUG_TECHTREE_PARSER needs DEBUG to be defined as well} {$ENDIF} {$ENDIF} uses {$IFDEF DEBUG_TECHTREE_PARSER} debug, {$ENDIF} sysutils, exceptions, tokenutils, actors, facilities; const TechnologyTreeNodeKindToString: array[TTechnologyTreeNodeKind] of UTF8String = ('Unknown', 'Breakthrough', 'Topic', 'Technology'); DefaultDifficulty = 0; DefaultMinimumDifficulty = 0; DefaultObscurity = 86400; // about one day kObscurityScaleFactor = 86400; // this value means the tech takes one day to be discovered, on average; smaller = faster, bigger = slower constructor TTechnologyTreeNodeHashTable.Create(PredictedCount: THashTableSizeInt = 8); begin inherited Create(@UTF8StringHash32, PredictedCount); end; constructor TTechnologyTreeManager.Create(Filename: UTF8String); var Message: TErrorString; ParsedTree: PParsedTechTreeBlock; begin inherited Create(); FFilename := Filename; {$IFDEF DEBUG_TECHTREE_PARSER} try {$ENDIF} ParsedTree := Parse(Message); if (not Assigned(ParsedTree)) then raise ESyntaxError.Create(Message); FTechnologyTree := Compile(ParsedTree, Message); Assert(not Assigned(ParsedTree)); if (not Assigned(FTechnologyTree)) then raise ESyntaxError.Create(Message); {$IFDEF DEBUG_TECHTREE_PARSER} except ReportException(); raise; end; {$ENDIF} end; destructor TTechnologyTreeManager.Destroy(); begin FreeCompiledTechTree(FTechnologyTree); inherited; end; constructor TTechnologyTreeManager.Read(Stream: TReadStream); var Count, Index, Subindex: Cardinal; Node: PTechnologyTreeNode; begin inherited; FFilename := Stream.ReadString(); Count := Stream.ReadCardinal(); if (Count > 0) then begin New(FTechnologyTree); SetLength(FTechnologyTree^.Nodes, Count); Assert(Count < High(THashTableSizeInt)); FTechnologyTree^.NameIndex := TTechnologyTreeNodeHashTable.Create(Count); // $R- Assert(Low(FTechnologyTree^.Nodes) < High(FTechnologyTree^.Nodes)); for Index := Low(FTechnologyTree^.Nodes) to High(FTechnologyTree^.Nodes) do // $R- begin New(Node); FTechnologyTree^.Nodes[Index] := Node; Node^.Name := Stream.ReadString(); Assert(not FTechnologyTree^.NameIndex.Has(Node^.Name)); FTechnologyTree^.NameIndex[Node^.Name] := Node; Node^.ID := Index; // $R- end; for Index := Low(FTechnologyTree^.Nodes) to High(FTechnologyTree^.Nodes) do // $R- begin Node := FTechnologyTree^.Nodes[Index]; Assert(SizeOf(TDependencyCount) = SizeOf(Byte)); Node^.DependencyCount := Stream.ReadByte(); SetLength(FTechnologyTree^.Nodes[Index]^.PrerequisiteFor, Stream.ReadCardinal()); if (Length(FTechnologyTree^.Nodes[Index]^.PrerequisiteFor) > 0) then for Subindex := Low(FTechnologyTree^.Nodes[Index]^.PrerequisiteFor) to High(FTechnologyTree^.Nodes[Index]^.PrerequisiteFor) do FTechnologyTree^.Nodes[Index]^.PrerequisiteFor[Subindex] := FTechnologyTree^.NameIndex[Stream.ReadString()]; Node^.Kind := TTechnologyTreeNodeKind(Stream.ReadByte()); case Node^.Kind of tkBreakthrough: begin Node^.Difficulty := Stream.ReadInteger(); Node^.MinimumDifficulty := Stream.ReadCardinal(); Node^.DiscoveryLambda := Stream.ReadDouble(); New(Node^.Bonuses); SetLength(Node^.Bonuses^, Stream.ReadCardinal()); if (Length(Node^.Bonuses^) > 0) then for Subindex := Low(Node^.Bonuses^) to High(Node^.Bonuses^) do begin Node^.Bonuses^[Subindex].PrerequisiteNode := FTechnologyTree^.NameIndex[Stream.ReadString()]; Node^.Bonuses^[Subindex].Bonus := Stream.ReadInteger(); end; New(Node^.News); SetLength(Node^.News^, Stream.ReadCardinal()); if (Length(Node^.News^) > 0) then for Subindex := Low(Node^.News^) to High(Node^.News^) do begin Node^.News^[Subindex].Name := Stream.ReadString(); Node^.News^[Subindex].Kind := Stream.ReadString(); Node^.News^[Subindex].Body := Stream.ReadString(); end; end; tkTopic: ; tkTechnology: begin Node^.Actor := TAbstractActorClass(Stream.ReadClass()); Node^.ActorSettings := Stream.ReadObject() as TAutoStorable; New(Node^.Facilities); SetLength(Node^.Facilities^, Stream.ReadCardinal()); if (Length(Node^.Facilities^) > 0) then for Subindex := Low(Node^.Facilities^) to High(Node^.Facilities^) do begin Node^.Facilities^[Subindex].Facility := TAbstractFacilityClass(Stream.ReadClass()); Node^.Facilities^[Subindex].FacilitySettings := Stream.ReadObject() as TAutoStorable; end; end; else Assert(False); end; end; for Index := Low(FTechnologyTree^.Nodes) to High(FTechnologyTree^.Nodes) do // $R- begin Node := FTechnologyTree^.Nodes[Index]; if ((Node^.Kind = tkBreakthrough) and (Length(Node^.Bonuses^) > 0)) then for Subindex := Low(Node^.Bonuses^) to High(Node^.Bonuses^) do // $R- Assert(Node^.Bonuses^[Subindex].PrerequisiteNode^.Kind in [tkBreakthrough, tkTopic], 'unexpected Kind of Prerequisite Node for Bonus: ' + GetEnumName(TypeInfo(Node^.Bonuses^[Subindex].PrerequisiteNode^.Kind), Ord(Node^.Bonuses^[Subindex].PrerequisiteNode^.Kind))); end; end; end; procedure TTechnologyTreeManager.Write(Stream: TWriteStream); var Index, Subindex: Cardinal; begin inherited; Stream.WriteString(FFilename); if (Assigned(FTechnologyTree)) then begin Stream.WriteCardinal(Length(FTechnologyTree^.Nodes)); Assert(Length(FTechnologyTree^.Nodes) = FTechnologyTree^.NameIndex.Count); Assert(Length(FTechnologyTree^.Nodes) > 0); for Index := Low(FTechnologyTree^.Nodes) to High(FTechnologyTree^.Nodes) do // $R- begin Stream.WriteString(FTechnologyTree^.Nodes[Index]^.Name); Assert(FTechnologyTree^.Nodes[Index]^.ID = Index); end; for Index := Low(FTechnologyTree^.Nodes) to High(FTechnologyTree^.Nodes) do // $R- begin Assert(SizeOf(TDependencyCount) = SizeOf(Byte)); Stream.WriteByte(FTechnologyTree^.Nodes[Index]^.DependencyCount); Stream.WriteCardinal(Length(FTechnologyTree^.Nodes[Index]^.PrerequisiteFor)); if (Length(FTechnologyTree^.Nodes[Index]^.PrerequisiteFor) > 0) then for Subindex := Low(FTechnologyTree^.Nodes[Index]^.PrerequisiteFor) to High(FTechnologyTree^.Nodes[Index]^.PrerequisiteFor) do Stream.WriteString(FTechnologyTree^.Nodes[Index]^.PrerequisiteFor[Subindex]^.Name); Stream.WriteByte(Byte(FTechnologyTree^.Nodes[Index]^.Kind)); case FTechnologyTree^.Nodes[Index]^.Kind of tkBreakthrough: begin Stream.WriteInteger(FTechnologyTree^.Nodes[Index]^.Difficulty); Stream.WriteCardinal(FTechnologyTree^.Nodes[Index]^.MinimumDifficulty); Stream.WriteDouble(FTechnologyTree^.Nodes[Index]^.DiscoveryLambda); Stream.WriteCardinal(Length(FTechnologyTree^.Nodes[Index]^.Bonuses^)); if (Length(FTechnologyTree^.Nodes[Index]^.Bonuses^) > 0) then for Subindex := Low(FTechnologyTree^.Nodes[Index]^.Bonuses^) to High(FTechnologyTree^.Nodes[Index]^.Bonuses^) do begin Stream.WriteString(FTechnologyTree^.Nodes[Index]^.Bonuses^[Subindex].PrerequisiteNode^.Name); Stream.WriteInteger(FTechnologyTree^.Nodes[Index]^.Bonuses^[Subindex].Bonus); end; Stream.WriteCardinal(Length(FTechnologyTree^.Nodes[Index]^.News^)); if (Length(FTechnologyTree^.Nodes[Index]^.News^) > 0) then for Subindex := Low(FTechnologyTree^.Nodes[Index]^.News^) to High(FTechnologyTree^.Nodes[Index]^.News^) do begin Stream.WriteString(FTechnologyTree^.Nodes[Index]^.News^[Subindex].Name); Stream.WriteString(FTechnologyTree^.Nodes[Index]^.News^[Subindex].Kind); Stream.WriteString(FTechnologyTree^.Nodes[Index]^.News^[Subindex].Body); end; end; tkTopic: ; tkTechnology: begin Stream.WriteClass(FTechnologyTree^.Nodes[Index]^.Actor); Stream.WriteObject(FTechnologyTree^.Nodes[Index]^.ActorSettings); Stream.WriteCardinal(Length(FTechnologyTree^.Nodes[Index]^.Facilities^)); if (Length(FTechnologyTree^.Nodes[Index]^.Facilities^) > 0) then for Subindex := Low(FTechnologyTree^.Nodes[Index]^.Facilities^) to High(FTechnologyTree^.Nodes[Index]^.Facilities^) do begin Stream.WriteClass(FTechnologyTree^.Nodes[Index]^.Facilities^[Subindex].Facility); Stream.WriteObject(FTechnologyTree^.Nodes[Index]^.Facilities^[Subindex].FacilitySettings); end; end; else Assert(False); end; end; end else begin Stream.WriteCardinal(0); end; end; procedure TTechnologyTreeManager.FreeParsedTechTree(var Victim: PParsedTechTreeBlock); var TempParsedTechTreeBonus: PParsedTechTreeBonus; TempParsedTechTreeFacility: PParsedFacilityDescriptions; TempBlock: PParsedTechTreeBlock; begin while (Assigned(Victim)) do begin case (Victim^.BlockType) of ptBreakthrough: begin while (Assigned(Victim^.BonusLines)) do begin TempParsedTechTreeBonus := Victim^.BonusLines; Victim^.BonusLines := Victim^.BonusLines^.Next; Dispose(TempParsedTechTreeBonus); end; end; ptTechnology: begin Victim^.ActorSettings.Free(); while (Assigned(Victim^.Facilities)) do begin Victim^.Facilities^.FacilitySettings.Free(); TempParsedTechTreeFacility := Victim^.Facilities; Victim^.Facilities := Victim^.Facilities^.Next; Dispose(TempParsedTechTreeFacility); end; end; ptNews: begin Dispose(Victim^.NewsKind); Dispose(Victim^.NewsBody); Dispose(Victim^.NewsFor); end; ptNone, ptTopic: ; end; //Victim^.BlockType := tkNone; TempBlock := Victim; Victim := Victim^.Next; Dispose(TempBlock); end; end; procedure TTechnologyTreeManager.FreeCompiledTechTree(var Tree: PTechnologyTree); var Index, Subindex: Cardinal; begin if (Assigned(Tree)) then begin Tree^.NameIndex.Free(); if (Length(Tree^.Nodes) > 0) then begin for Index := Low(Tree^.Nodes) to High(Tree^.Nodes) do begin case (Tree^.Nodes[Index]^.Kind) of tkBreakthrough: begin if (Assigned(Tree^.Nodes[Index]^.Bonuses)) then Dispose(Tree^.Nodes[Index]^.Bonuses); Assert(Assigned(Tree^.Nodes[Index]^.News)); Dispose(Tree^.Nodes[Index]^.News); end; tkTechnology: begin Tree^.Nodes[Index]^.ActorSettings.Free(); if (Assigned(Tree^.Nodes[Index]^.Facilities)) then begin if (Length(Tree^.Nodes[Index]^.Facilities^) > 0) then for Subindex := Low(Tree^.Nodes[Index]^.Facilities^) to High(Tree^.Nodes[Index]^.Facilities^) do Tree^.Nodes[Index]^.Facilities^[Subindex].FacilitySettings.Free(); Dispose(Tree^.Nodes[Index]^.Facilities); end; end; end; Dispose(Tree^.Nodes[Index]); end; end; Dispose(Tree); Tree := nil; end; end; function TTechnologyTreeManager.Parse(out ErrorMessage: TErrorString): PParsedTechTreeBlock; var ParsedBlocks: PParsedTechTreeBlock; CurrentBlock: PParsedTechTreeBlock; function PushBlock(CreateNew: Boolean): Boolean; begin if (CurrentBlock^.BlockType <> ptNone) then begin if (CurrentBlock^.BlockType = ptTechnology) then begin if (not Assigned(CurrentBlock^.Actor)) then begin ErrorMessage := 'Technology block "' + CurrentBlock^.Name + '" has no Implementation line.'; Result := False; Exit; end; end else if (CurrentBlock^.BlockType = ptNews) then begin if (CurrentBlock^.NewsKind^ = '') then begin ErrorMessage := 'News block "' + CurrentBlock^.Name + '" has no Kind line.'; Result := False; Exit; end else if (CurrentBlock^.NewsBody^ = '') then begin ErrorMessage := 'News block "' + CurrentBlock^.Name + '" has no Body line.'; Result := False; Exit; end else if (CurrentBlock^.NewsFor^ = '') then begin ErrorMessage := 'News block "' + CurrentBlock^.Name + '" has no For line.'; Result := False; Exit; end; end; CurrentBlock^.Next := ParsedBlocks; ParsedBlocks := CurrentBlock; if (CreateNew) then begin New(CurrentBlock); CurrentBlock^.BlockType := ptNone; CurrentBlock^.Next := nil; end else begin {$IFOPT C+} CurrentBlock := nil; {$ENDIF} end; end else if (not CreateNew) then begin {$IFDEF DEBUG_TECHTREE_PARSER} Writeln('disposing of CurrentBlock'); {$ENDIF} Dispose(CurrentBlock); {$IFOPT C+} CurrentBlock := nil; {$ENDIF} end; Result := True; end; var TechFile: Text; Line, Key, Value: UTF8String; LineNumber, ColonIndex, TokenIndex: Cardinal; Tokens: TTokenArray; BonusLines: PParsedTechTreeBonus; FacilityBlock: PParsedFacilityDescriptions; begin {$IFDEF DEBUG_TECHTREE_PARSER} try {$ENDIF} {$IFDEF DEBUG_TECHTREE_PARSER} SetHeapInfo('Parser: top'); {$ENDIF} ErrorMessage := ''; Assign(TechFile, FFilename); Reset(TechFile); LineNumber := 0; ParsedBlocks := nil; {$IFDEF DEBUG_TECHTREE_PARSER} SetHeapInfo('Parser: first CurrentBlock'); {$ENDIF} New(CurrentBlock); {$IFDEF DEBUG_TECHTREE_PARSER} SetHeapInfo('Parser: setup'); {$ENDIF} CurrentBlock^.BlockType := ptNone; CurrentBlock^.Next := nil; Assert(CurrentBlock^.Name = ''); Assert(Length(CurrentBlock^.Needs) = 0); {$IFDEF DEBUG_TECHTREE_PARSER} SetHeapInfo('Parser: loop'); {$ENDIF} while ((ErrorMessage = '') and (not EOF(TechFile))) do begin Inc(LineNumber); Readln(TechFile, Line); // XXX should trim spaces from the line if (Length(Line) = 0) then Continue; // blank lines are ignored if (Line[1] = '#') then Continue; // lines starting with a hash are comment lines ColonIndex := Pos(':', Line); if (ColonIndex = 0) then begin Key := Line; Value := ''; end else begin if (ColonIndex > 1) then Key := Copy(Line, 1, ColonIndex-1) else Key := ''; Inc(ColonIndex); if ((ColonIndex <= Length(Line)) and (Line[ColonIndex] = ' ')) then Inc(ColonIndex); if (ColonIndex <= Length(Line)) then Value := Copy(Line, ColonIndex, Length(Line)-ColonIndex+1) else Value := ''; end; {$IFDEF DEBUG_TECHTREE_PARSER} SetHeapInfo('Parser: line data processing'); {$ENDIF} if (Key = 'Breakthrough') then begin if (Value = '') then begin ErrorMessage := Key + ' block has no name, but must have one'; end else if (PushBlock(True)) then begin CurrentBlock^.Name := Value; CurrentBlock^.BlockType := ptBreakthrough; CurrentBlock^.Difficulty := DefaultDifficulty; CurrentBlock^.MinimumDifficulty := DefaultMinimumDifficulty; CurrentBlock^.Obscurity := DefaultObscurity; CurrentBlock^.HadDifficulty := False; CurrentBlock^.HadMinimumDifficulty := False; CurrentBlock^.HadObscurity := False; CurrentBlock^.BonusLines := nil; end; end else if (Key = 'Topic') then begin if (Value = '') then begin ErrorMessage := Key + ' block has no name, but must have one'; end else if (PushBlock(True)) then begin CurrentBlock^.Name := Value; CurrentBlock^.BlockType := ptTopic; end; end else if (Key = 'Technology') then begin if (Value = '') then begin ErrorMessage := Key + ' block has no name, but must have one'; end else if (PushBlock(True)) then begin CurrentBlock^.Name := Value; CurrentBlock^.BlockType := ptTechnology; CurrentBlock^.Actor := nil; CurrentBlock^.ActorSettings := nil; CurrentBlock^.Facilities := nil; end; end else if (Key = 'News') then begin if (Value = '') then begin ErrorMessage := Key + ' block has no name, but must have one'; end else if (PushBlock(True)) then begin CurrentBlock^.Name := Value; CurrentBlock^.BlockType := ptNews; New(CurrentBlock^.NewsKind); New(CurrentBlock^.NewsBody); New(CurrentBlock^.NewsFor); end; end else if (CurrentBlock^.BlockType <> ptNone) then begin {$IFDEF DEBUG_TECHTREE_PARSER} SetHeapInfo('Parser: block internal'); {$ENDIF} if ((Key = 'Needs') and (CurrentBlock^.BlockType in [ptBreakthrough, ptTopic, ptTechnology])) then begin SetLength(CurrentBlock^.Needs, Length(CurrentBlock^.Needs)+1); CurrentBlock^.Needs[High(CurrentBlock^.Needs)] := Value; end else if (CurrentBlock^.BlockType = ptBreakthrough) then begin if (Key = 'Difficulty') then begin if (CurrentBlock^.HadDifficulty) then begin ErrorMessage := 'Duplicate property "' + Key + '" in Breakthrough block'; end else begin Tokens := GetLineTokens(Value); if ((Length(Tokens) <> 1) or (Tokens[0].TokenType <> ttInteger)) then begin ErrorMessage := 'Difficulty line in Breakthrough block has wrong syntax; correct syntax is "", e.g. "10"'; end else begin CurrentBlock^.Difficulty := Tokens[0].IntegerValue; end; FreeLineTokens(Tokens); CurrentBlock^.HadDifficulty := True; end; end else if (Key = 'Minimum Difficulty') then begin if (CurrentBlock^.HadMinimumDifficulty) then begin ErrorMessage := 'Duplicate property "' + Key + '" in Breakthrough block'; end else begin Tokens := GetLineTokens(Value); if ((Length(Tokens) <> 1) or (Tokens[0].TokenType <> ttInteger)) then begin ErrorMessage := 'Minimum Difficulty line in Breakthrough block has wrong syntax; correct syntax is "", e.g. "10"'; end else begin CurrentBlock^.MinimumDifficulty := Tokens[0].IntegerValue; end; FreeLineTokens(Tokens); CurrentBlock^.HadMinimumDifficulty := True; end; end else if (Key = 'Obscurity') then begin if (CurrentBlock^.HadObscurity) then begin ErrorMessage := 'Duplicate property "' + Key + '" in Breakthrough block'; end else begin Tokens := GetLineTokens(Value); if ((Length(Tokens) <> 1) or (Tokens[0].TokenType <> ttInteger)) then begin ErrorMessage := 'Obscurity line in Breakthrough block has wrong syntax; correct syntax is "", e.g. "10"'; end else if ((Tokens[0].IntegerValue <= 0)) then begin ErrorMessage := 'Obscurity line in Breakthrough block has value less than or equal to zero; obscurity must be positive'; end else begin CurrentBlock^.Obscurity := Tokens[0].IntegerValue; end; FreeLineTokens(Tokens); CurrentBlock^.HadObscurity := True; end; end else if (Key = 'Topic Bonus') then begin Tokens := GetLineTokens(Value, 'from'); if ((Length(Tokens) <> 5) or (Tokens[0].TokenType <> ttInteger) or (Tokens[1].TokenType <> ttString) or (Tokens[1].StringValue^ <> 'per') or (Tokens[2].TokenType <> ttString) or (Tokens[2].StringValue^ <> 'second') or (Tokens[3].TokenType <> ttString) or (Tokens[3].StringValue^ <> 'from') or (Tokens[4].TokenType <> ttString)) then begin ErrorMessage := 'Topic Bonus line in Breakthrough block uses wrong syntax; correct syntax is " per second from ", e.g. "5 per second from Television Broadcasting"'; end else begin New(BonusLines); Assert(BonusLines^.Name = ''); BonusLines^.Kind := tkTopic; BonusLines^.Bonus := Tokens[0].IntegerValue; BonusLines^.Name := Tokens[4].StringValue^; BonusLines^.Next := CurrentBlock^.BonusLines; CurrentBlock^.BonusLines := BonusLines; end; FreeLineTokens(Tokens); end else if (Key = 'Breakthrough Bonus') then begin Tokens := GetLineTokens(Value, 'from'); if ((Length(Tokens) <> 3) or (Tokens[0].TokenType <> ttInteger) or (Tokens[1].TokenType <> ttString) or (Tokens[1].StringValue^ <> 'from') or (Tokens[2].TokenType <> ttString)) then begin ErrorMessage := 'Breakthrough Bonus line in Breakthrough block uses wrong syntax; correct syntax is " from ", e.g. "5 from Television Broadcasting"'; end else begin New(BonusLines); Assert(BonusLines^.Name = ''); BonusLines^.Kind := tkBreakthrough; BonusLines^.Bonus := Tokens[0].IntegerValue; BonusLines^.Name := Tokens[2].StringValue^; BonusLines^.Next := CurrentBlock^.BonusLines; CurrentBlock^.BonusLines := BonusLines; end; FreeLineTokens(Tokens); end else begin ErrorMessage := 'Unknown property "' + Key + '" in Breakthrough block'; end; end else if (CurrentBlock^.BlockType = ptTopic) then begin ErrorMessage := 'Unknown property "' + Key + '" in Topic block'; end else if (CurrentBlock^.BlockType = ptTechnology) then begin if (Key = 'Implementation') then begin Tokens := GetLineTokens(Value); if ((Length(Tokens) < 1) or (Tokens[0].TokenType <> ttString)) then begin ErrorMessage := 'Implementation line is missing class name'; end else begin CurrentBlock^.Actor := GetActorClass(Tokens[0].StringValue^); if (not Assigned(CurrentBlock^.Actor)) then begin ErrorMessage := 'Class name ' + Tokens[0].StringValue^ + ' not known'; end else begin TokenIndex := 0; Tokens[0].StringValue^ := CurrentBlock^.Name; CurrentBlock^.ActorSettings := CurrentBlock^.Actor.ParseSettings(Tokens, TokenIndex, ErrorMessage); if (ErrorMessage <> '') then begin Assert(not Assigned(CurrentBlock^.ActorSettings)); ErrorMessage := ErrorMessage + ''; end else if (TokenIndex < Length(Tokens)) then begin Assert(TokenIndex > 0); ErrorMessage := 'Too many tokens (' + IntToStr(Length(Tokens)) + '; expected ' + IntToStr(TokenIndex) + ') on Implementation line ' + IntToStr(LineNumber); end else begin Assert(TokenIndex = Length(Tokens)); Assert(Assigned(CurrentBlock^.ActorSettings)); // not really necessary in theory, but in practice for now it is end; end; end; FreeLineTokens(Tokens); end else if (Key = 'Facility') then begin Tokens := GetLineTokens(Value); if ((Length(Tokens) < 1) or (Tokens[0].TokenType <> ttString)) then begin ErrorMessage := 'Facility line is missing class name'; end else begin New(FacilityBlock); FacilityBlock^.Facility := GetFacilityClass(Tokens[0].StringValue^); FacilityBlock^.FacilitySettings := nil; if (not Assigned(FacilityBlock^.Facility)) then begin ErrorMessage := 'Class name ' + Tokens[0].StringValue^ + ' not known'; end else begin TokenIndex := 1; FacilityBlock^.FacilitySettings := FacilityBlock^.Facility.ParseSettings(Tokens, TokenIndex, ErrorMessage); if (ErrorMessage <> '') then begin Assert(not Assigned(FacilityBlock^.FacilitySettings)); ErrorMessage := ErrorMessage + ' on line ' + IntToStr(LineNumber) end else if (TokenIndex < Length(Tokens)) then begin Assert(TokenIndex > 0); ErrorMessage := 'Too many tokens (' + IntToStr(Length(Tokens)) + '; expected ' + IntToStr(TokenIndex) + ') on Facility line ' + IntToStr(LineNumber); if (Assigned(FacilityBlock^.FacilitySettings)) then begin FacilityBlock^.FacilitySettings.Free(); FacilityBlock^.FacilitySettings := nil; end; end else Assert(TokenIndex = Length(Tokens)); end; if (ErrorMessage <> '') then begin Assert(not Assigned(FacilityBlock^.FacilitySettings)); Dispose(FacilityBlock); end else begin FacilityBlock^.Next := CurrentBlock^.Facilities; CurrentBlock^.Facilities := FacilityBlock; end; end; FreeLineTokens(Tokens); end else begin ErrorMessage := 'Unknown property "' + Key + '" in Technology block'; end; end else if (CurrentBlock^.BlockType = ptNews) then begin if (Key = 'Kind') then begin if (CurrentBlock^.NewsKind^ <> '') then begin ErrorMessage := 'Duplicate property "' + Key + '" in News block'; end else if (Value = '') then begin ErrorMessage := 'Property "' + Key + '" is empty in News block'; end else begin CurrentBlock^.NewsKind^ := Value; end; end else if (Key = 'Body') then begin if (CurrentBlock^.NewsBody^ <> '') then CurrentBlock^.NewsBody^ := CurrentBlock^.NewsBody^ + #10 + Value else CurrentBlock^.NewsBody^ := Value; end else if (Key = 'For') then begin if (CurrentBlock^.NewsFor^ <> '') then begin ErrorMessage := 'Duplicate property "' + Key + '" in News block'; end else if (Value = '') then begin ErrorMessage := 'Property "' + Key + '" is empty in News block'; end else begin CurrentBlock^.NewsFor^ := Value; end; end else begin ErrorMessage := 'Unknown property "' + Key + '" in News block'; end; end else Assert(False); {$IFDEF DEBUG_TECHTREE_PARSER} SetHeapInfo('Parser: after block internal'); {$ENDIF} end else begin ErrorMessage := 'Unknown block type "' + Key + '"'; end; end; Close(TechFile); {$IFDEF DEBUG_TECHTREE_PARSER} SetHeapInfo('Parser: after file close'); {$ENDIF} if (ErrorMessage = '') then begin {$IFDEF DEBUG_TECHTREE_PARSER} Writeln('Parser: no error'); {$ENDIF} if (PushBlock(False)) then begin if (not Assigned(ParsedBlocks)) then begin ErrorMessage := FFileName + ':0:Input file did not contain any data blocks'; end; end else begin FreeParsedTechTree(CurrentBlock); FreeParsedTechTree(ParsedBlocks); end; end else begin {$IFDEF DEBUG_TECHTREE_PARSER} SetHeapInfo('Parser: error'); {$ENDIF} {$IFDEF DEBUG_TECHTREE_PARSER} Writeln('ERROR'); if (Assigned(CurrentBlock)) then Writeln('CurrentBlock assigned'); {$ENDIF} FreeParsedTechTree(CurrentBlock); FreeParsedTechTree(ParsedBlocks); ErrorMessage := FFileName + ':' + IntToStr(LineNumber) + ':' + ErrorMessage; end; Result := ParsedBlocks; {$IFDEF DEBUG_TECHTREE_PARSER} finally Writeln('Exiting Parse() with message: "', ErrorMessage, '"'); {$IFDEF DEBUG_TECHTREE_PARSER} SetHeapInfo('Parser: bottom'); {$ENDIF} end; {$ENDIF} end; function TTechnologyTreeManager.Compile(var ParsedTechTree: PParsedTechTreeBlock; out ErrorMessage: TErrorString): PTechnologyTree; function FillBonuses(ParsedBonuses: PParsedTechTreeBonus; CompiledBonuses: TTechnologyTreeNode.PBreakthroughBonusArray; NameIndex: TTechnologyTreeNodeHashTable): Boolean; var Count: Cardinal; TempParsedBonuses: PParsedTechTreeBonus; Node: PTechnologyTreeNode; begin Result := True; Count := 0; TempParsedBonuses := ParsedBonuses; while (Assigned(TempParsedBonuses)) do begin Inc(Count); TempParsedBonuses := TempParsedBonuses^.Next; end; if (Count > 0) then begin SetLength(CompiledBonuses^, Count); Count := 0; TempParsedBonuses := ParsedBonuses; while (Assigned(TempParsedBonuses)) do begin if (not NameIndex.Has(TempParsedBonuses^.Name)) then begin Result := False; ErrorMessage := TechnologyTreeNodeKindToString[NameIndex[TempParsedBonuses^.Name]^.Kind] + ' Bonus refers to "' + TempParsedBonuses^.Name + '" which is not defined; defined ' + TechnologyTreeNodeKindToString[NameIndex[TempParsedBonuses^.Name]^.Kind] + ' names are:'; for Node in NameIndex.Values do if (Node^.Kind = TempParsedBonuses^.Kind) then ErrorMessage := ErrorMessage + ' "' + Node^.Name + '"'; Exit; end; if (NameIndex[TempParsedBonuses^.Name]^.Kind <> TempParsedBonuses^.Kind) then begin Result := False; ErrorMessage := TechnologyTreeNodeKindToString[NameIndex[TempParsedBonuses^.Name]^.Kind] + ' Bonus refers to "' + TempParsedBonuses^.Name + '" which is a ' + TechnologyTreeNodeKindToString[NameIndex[TempParsedBonuses^.Name]^.Kind]; Exit; end; CompiledBonuses^[Count].PrerequisiteNode := NameIndex[TempParsedBonuses^.Name]; CompiledBonuses^[Count].Bonus := TempParsedBonuses^.Bonus; Assert(Assigned(CompiledBonuses^[Count].PrerequisiteNode)); Inc(Count); TempParsedBonuses := TempParsedBonuses^.Next; end; end; end; function FillFacilities(ParsedFacilities: PParsedFacilityDescriptions; CompiledFacilities: TTechnologyTreeNode.PFacilityDescriptionArray): Boolean; var Count: Cardinal; TempParsedFacility: PParsedFacilityDescriptions; begin Result := True; Count := 0; TempParsedFacility := ParsedFacilities; while (Assigned(TempParsedFacility)) do begin Inc(Count); TempParsedFacility := TempParsedFacility^.Next; end; if (Count > 0) then begin SetLength(CompiledFacilities^, Count); Count := 0; TempParsedFacility := ParsedFacilities; while (Assigned(TempParsedFacility)) do begin // XXX should check that none of the facilities have duplicate properties or methods CompiledFacilities^[Count].Facility := TempParsedFacility^.Facility; CompiledFacilities^[Count].FacilitySettings := TempParsedFacility^.FacilitySettings; TempParsedFacility^.FacilitySettings := nil; // so it doesn't get freed when we free the parsed block Assert(Assigned(CompiledFacilities^[Count].Facility)); Inc(Count); TempParsedFacility := TempParsedFacility^.Next; end; end; end; function ConnectNews(ParsedNode: PParsedTechTreeBlock; NameIndex: TTechnologyTreeNodeHashTable): Boolean; var TechnologyNode: PTechnologyTreeNode; News: ^TNewsNode; begin Assert(ParsedNode^.BlockType = ptNews); Assert(ParsedNode^.Name <> ''); Assert(ParsedNode^.NewsFor^ <> ''); Assert(ParsedNode^.NewsKind^ <> ''); Assert(ParsedNode^.NewsBody^ <> ''); Assert(Length(ParsedNode^.Needs) = 0); if (NameIndex.Has(ParsedNode^.NewsFor^)) then begin TechnologyNode := NameIndex[ParsedNode^.NewsFor^]; if (TechnologyNode^.Kind <> tkBreakthrough) then begin Result := False; ErrorMessage := 'A News block refers in its For line to block "' + ParsedNode^.NewsFor^ + '", which is not a Breakthrough block but a ' + TechnologyTreeNodeKindToString[TechnologyNode^.Kind] + ' block'; end else begin Result := True; Assert(Assigned(TechnologyNode^.News)); SetLength(TechnologyNode^.News^, Length(TechnologyNode^.News^)+1); News := @(TechnologyNode^.News^[High(TechnologyNode^.News^)]); News^.Name := ParsedNode^.Name; News^.Kind := ParsedNode^.NewsKind^; News^.Body := ParsedNode^.NewsBody^; end; end else begin Result := False; ErrorMessage := 'A News block refers to a non-existent block "' + ParsedNode^.NewsFor^ + '" in its For line'; end; end; var TempBlock: PParsedTechTreeBlock; Prereq, Target: PTechnologyTreeNode; BlockCount, Index: Cardinal; begin Assert(Assigned(ParsedTechTree)); // Count the number of bits so that we can make the tech tree BlockCount := 0; TempBlock := ParsedTechTree; while (Assigned(TempBlock)) do begin if (TempBlock^.BlockType in ptTechTreeBlockTypes) then begin Inc(BlockCount); end else begin Assert(TempBlock^.BlockType = ptNews); end; TempBlock := TempBlock^.Next; end; // Prepare the structures New(Result); Assert(Length(Result^.Nodes) = 0); SetLength(Result^.Nodes, BlockCount); for Index := Low(Result^.Nodes) to High(Result^.Nodes) do begin New(Result^.Nodes[Index]); Assert(Result^.Nodes[Index]^.Name = ''); Assert(Length(Result^.Nodes[Index]^.PrerequisiteFor) = 0); Result^.Nodes[Index]^.Kind := tkNone; end; Result^.NameIndex := TTechnologyTreeNodeHashTable.Create(BlockCount); // Fill in the basic block data (no cross-references) Index := High(Result^.Nodes)+1; TempBlock := ParsedTechTree; while (Assigned(TempBlock)) do begin if (TempBlock^.BlockType in ptTechTreeBlockTypes) then begin Dec(Index); if (Result^.NameIndex.Has(TempBlock^.Name)) then begin ErrorMessage := 'Name "' + TempBlock^.Name + '" used twice'; FreeParsedTechTree(ParsedTechTree); FreeCompiledTechTree(Result); Exit; end; Result^.Nodes[Index]^.Name := TempBlock^.Name; Result^.Nodes[Index]^.ID := Index; Result^.Nodes[Index]^.DependencyCount := 0; Assert(Result^.Nodes[Index]^.Kind = tkNone); Assert(TempBlock^.BlockType <> ptNone); Result^.Nodes[Index]^.Kind := TTechnologyTreeNodeKind(TempBlock^.BlockType); Result^.NameIndex[TempBlock^.Name] := Result^.Nodes[Index]; case (TempBlock^.BlockType) of ptBreakthrough: begin Result^.Nodes[Index]^.Difficulty := TempBlock^.Difficulty; Result^.Nodes[Index]^.MinimumDifficulty := TempBlock^.MinimumDifficulty; Result^.Nodes[Index]^.DiscoveryLambda := kObscurityScaleFactor / TempBlock^.Obscurity; Result^.Nodes[Index]^.Bonuses := nil; // gets allocated below New(Result^.Nodes[Index]^.News); end; ptTopic: ; ptTechnology: begin Result^.Nodes[Index]^.Actor := TempBlock^.Actor; Result^.Nodes[Index]^.ActorSettings := TempBlock^.ActorSettings; TempBlock^.ActorSettings := nil; // transfer ownership Result^.Nodes[Index]^.Facilities := nil; // gets allocated below end; else Assert(False); end; end; TempBlock := TempBlock^.Next; end; // Fill in the cross-references Index := High(Result^.Nodes)+1; TempBlock := ParsedTechTree; while (Assigned(TempBlock)) do begin if (TempBlock^.BlockType in ptTechTreeBlockTypes) then Dec(Index); case (TempBlock^.BlockType) of ptBreakthrough: begin Assert(not Assigned(Result^.Nodes[Index]^.Bonuses)); New(Result^.Nodes[Index]^.Bonuses); Assert(Length(Result^.Nodes[Index]^.Bonuses^) = 0); if (not FillBonuses(TempBlock^.BonusLines, Result^.Nodes[Index]^.Bonuses, Result^.NameIndex)) then begin FreeParsedTechTree(ParsedTechTree); FreeCompiledTechTree(Result); Exit; end; end; ptTopic: ; ptTechnology: begin Assert(not Assigned(Result^.Nodes[Index]^.Facilities)); New(Result^.Nodes[Index]^.Facilities); Assert(Length(Result^.Nodes[Index]^.Facilities^) = 0); if (not FillFacilities(TempBlock^.Facilities, Result^.Nodes[Index]^.Facilities)) then begin FreeParsedTechTree(ParsedTechTree); FreeCompiledTechTree(Result); Exit; end; end; ptNews: begin if (not ConnectNews(TempBlock, Result^.NameIndex)) then begin FreeParsedTechTree(ParsedTechTree); FreeCompiledTechTree(Result); Exit; end; end; else Assert(False); end; TempBlock := TempBlock^.Next; end; // Fill in the dependencies TempBlock := ParsedTechTree; while (Assigned(TempBlock)) do begin if (Length(TempBlock^.Needs) > 0) then for Index := Low(TempBlock^.Needs) to High(TempBlock^.Needs) do begin if (not Result^.NameIndex.Has(TempBlock^.Needs[Index])) then begin ErrorMessage := 'Block "' + TempBlock^.Name + '" depends on non-existent "' + TempBlock^.Needs[Index] + '"'; FreeParsedTechTree(ParsedTechTree); FreeCompiledTechTree(Result); Exit; end; Prereq := Result^.NameIndex[TempBlock^.Needs[Index]]; if (Prereq^.Kind <> tkBreakthrough) then begin ErrorMessage := 'Block "' + TempBlock^.Name + '" depends on "' + TempBlock^.Needs[Index] + '" which is not a Breakthrough block'; FreeParsedTechTree(ParsedTechTree); FreeCompiledTechTree(Result); Exit; end; Target := Result^.NameIndex[TempBlock^.Name]; Inc(Target^.DependencyCount); SetLength(Prereq^.PrerequisiteFor, Length(Prereq^.PrerequisiteFor)+1); Prereq^.PrerequisiteFor[High(Prereq^.PrerequisiteFor)] := Target; end; TempBlock := TempBlock^.Next; end; // Report Success ErrorMessage := ''; FreeParsedTechTree(ParsedTechTree); end; function TTechnologyTreeManager.Update(NewTree: PTechnologyTree; out ErrorMessage: TErrorString): Boolean; begin Result := XXX; // figure out the mapping from the current tree to the new tree // fail if there's any missing nodes // pass the mapping over to anyone who uses this tech tree // do the update end; function TTechnologyTreeManager.GetFreshTechTreeStatusArray(): TTechnologyTreeNodeStatusArray; var Index: Cardinal; begin Assert(Assigned(FTechnologyTree)); SetLength(Result, Length(FTechnologyTree^.Nodes)); if (Length(FTechnologyTree^.Nodes) > 0) then for Index := Low(FTechnologyTree^.Nodes) to High(FTechnologyTree^.Nodes) do begin Result[Index].PendingDependencies := FTechnologyTree^.Nodes[Index]^.DependencyCount; Assert(Result[Index].TimeSinceUnlocked = 0.0); end; end; procedure TTechnologyTreeManager.FilterTechTree(Kinds: TTechnologyTreeNodeKindSet; Callback: TTechnologyTreeFilterProc); var Index: Cardinal; begin if (Length(FTechnologyTree^.Nodes) > 0) then for Index := Low(FTechnologyTree^.Nodes) to High(FTechnologyTree^.Nodes) do if (FTechnologyTree^.Nodes[Index]^.Kind in Kinds) then Callback(Index, FTechnologyTree^.Nodes[Index]^.Name, FTechnologyTree^.Nodes[Index]^.Kind); end; function TTechnologyTreeManager.GetIDFor(Name: UTF8String; Kinds: TTechnologyTreeNodeKindSet): TTechnologyTreeNodeIDOrNone; var Node: PTechnologyTreeNode; begin Node := FTechnologyTree^.NameIndex[Name]; if (Assigned(Node) and (Node^.Kind in Kinds)) then Result := Node^.ID else Result := kNone; end; function TTechnologyTreeManager.GetPointsForBreakthrough(ID: TTechnologyTreeNodeID; const Status: TTechnologyTreeNodeStatusArray; Topic: TTechnologyTreeNodeIDOrNone; TimeStudiedAtStart, Interval: TDateTime; out Lambda: Double): Double; var Index: Cardinal; TopicNode: PTechnologyTreeNode; begin Assert(FTechnologyTree^.Nodes[ID]^.Kind = tkBreakthrough); Result := FTechnologyTree^.Nodes[ID]^.Difficulty; Lambda := FTechnologyTree^.Nodes[ID]^.DiscoveryLambda; if (Topic <> kNone) then begin TopicNode := FTechnologyTree^.Nodes[Topic]; Assert(TopicNode^.Kind = tkTopic); end else TopicNode := nil; if (Length(FTechnologyTree^.Nodes[ID]^.Bonuses^) > 0) then begin for Index := Low(FTechnologyTree^.Nodes[ID]^.Bonuses^) to High(FTechnologyTree^.Nodes[ID]^.Bonuses^) do begin if (FTechnologyTree^.Nodes[ID]^.Bonuses^[Index].PrerequisiteNode = TopicNode) then begin // it's a topic bonus Result := Result - FTechnologyTree^.Nodes[ID]^.Bonuses^[Index].Bonus * (TimeStudiedAtStart + Interval/2.0) * kSecondsPerDay; end else if (FTechnologyTree^.Nodes[ID]^.Bonuses^[Index].PrerequisiteNode^.Kind = tkBreakthrough) then begin if (Status[FTechnologyTree^.Nodes[ID]^.Bonuses^[Index].PrerequisiteNode^.ID].PendingDependencies = 0) then begin Result := Result - FTechnologyTree^.Nodes[ID]^.Bonuses^[Index].Bonus; end; end; end; end; if (Result < FTechnologyTree^.Nodes[ID]^.MinimumDifficulty) then Result := FTechnologyTree^.Nodes[ID]^.MinimumDifficulty; end; function TTechnologyTreeManager.Discover(ID: TTechnologyTreeNodeID; var Status: TTechnologyTreeNodeStatusArray; Dynasty: TAbstractDynasty): TTechnologyTreeNodeKindSet; var Node: PTechnologyTreeNode; Index: Cardinal; NewsItem: TNewsActor; begin Result := []; Node := FTechnologyTree^.Nodes[ID]; Assert(Assigned(Node)); Assert(Node^.Kind = tkBreakthrough); Assert(Assigned(Node^.News)); Assert(Status[Node^.ID].PendingDependencies = 0); Status[Node^.ID].Discovered := True; if (Length(Node^.PrerequisiteFor) > 0) then for Index := Low(Node^.PrerequisiteFor) to High(Node^.PrerequisiteFor) do begin Assert(Status[Node^.PrerequisiteFor[Index]^.ID].PendingDependencies > 0); Dec(Status[Node^.PrerequisiteFor[Index]^.ID].PendingDependencies); if (Status[Node^.PrerequisiteFor[Index]^.ID].PendingDependencies = 0) then Include(Result, Node^.PrerequisiteFor[Index]^.Kind); end; if (Length(Node^.News^) > 0) then for Index := Low(Node^.News^) to High(Node^.News^) do begin NewsItem := TNewsActor.Create(Node^.News^[Index].Name, Node^.News^[Index].Kind, Node^.News^[Index].Body); Dynasty.AddNewsItem(NewsItem); end; end; procedure TTechnologyTreeManager.DescribeTechnologies(Technologies: TTechnologyTreeNodeArray; Stream: TStringStreamWriter); var Index: Cardinal; begin Stream.WriteCardinal(Length(Technologies)); if (Length(Technologies) > 0) then for Index := Low(Technologies) to High(Technologies) do begin Assert(FTechnologyTree^.Nodes[Technologies[Index]]^.Kind = tkTechnology); Stream.WriteString(FTechnologyTree^.Nodes[Technologies[Index]]^.Name); Stream.WriteString(FTechnologyTree^.Nodes[Technologies[Index]]^.Actor.GetImage(FTechnologyTree^.Nodes[Technologies[Index]]^.ActorSettings)); Stream.WriteString(FTechnologyTree^.Nodes[Technologies[Index]]^.Actor.GetEnvironment(FTechnologyTree^.Nodes[Technologies[Index]]^.ActorSettings)); end; end; function TTechnologyTreeManager.Build(ID: TTechnologyTreeNodeID; Dynasty: TAbstractDynasty): TAbstractActor; var Index: Cardinal; Facility: TAbstractFacility; begin Result := FTechnologyTree^.Nodes[ID]^.Actor.Create(Dynasty, FTechnologyTree^.Nodes[ID]^.ActorSettings); if (Assigned(FTechnologyTree^.Nodes[ID]^.Facilities) and (Length(FTechnologyTree^.Nodes[ID]^.Facilities^) > 0)) then for Index := Low(FTechnologyTree^.Nodes[ID]^.Facilities^) to High(FTechnologyTree^.Nodes[ID]^.Facilities^) do // $R- begin Facility := FTechnologyTree^.Nodes[ID]^.Facilities^[Index].Facility.Create(Result, FTechnologyTree^.Nodes[ID]^.Facilities^[Index].FacilitySettings); Result.AddFacility(Facility); end; end; initialization {$INCLUDE registrations/techtree.inc} end.