Update pb-to-cpp.uc

Update pb-to-cpp.uc

DLL Export (and Alias)

# pb-to-cpp.uc - uCalc Transformation file
# This file was saved with uCalc Transform 2.0 on 1/13/2014 12:12:16 PM
# Comment: Converts PB source code to C++; modified by Daniel Corbier

ExternalKeywords: Exclude, Comment, Selected, ParentChild, FindMode, OutputFile, BatchAction, SEND
ExternalKeywords: Highlight, ForeColor, BackColor, FontName, FontSize, FontStyle
ExternalKeywords: FilterEndText, FilterSeparator, FilterSort, FilterSortFunc, FilterStartText, FilterUnique

FindMode: Replace

# Definitions

# Search Criteria

Criteria: 0
Enabled: True
Exclude: False
Comment: Converts PB source code to C++; modified by Daniel Corbier
Selected: False
Highlight: False
ForeColor: ControlText
BackColor: Aqua
FontName:
FontSize:
FontStyle:
CaseSensitive: False
QuoteSensitive: True
CodeBlockSensitive: True
FilterEndText:
FilterSeparator: {#10}
FilterSort: False
FilterSortFunc:
FilterStartText:
FilterUnique: False
Min: 0
Max: -1
MinSoft: 0
MaxSoft: -1
BatchAction: Transform
OutputFile:
SEND:
StartAfter: 0
StopAfter: -1
SkipOver: False
ParentChild: 0
Pass: 0
PassOnce: True
Precedence: 0
RightToLeft: False

Criteria: 1
Highlight: True
PassOnce: False
Find: {@Start}
Replace: // This file ({@Eval: Extract(ShortName(InputFile), ".")}.cpp) was converted from {@Eval: Extract(ShortName(InputFile), ".")}.Bas
         // with {@Eval: AppName} on {@Eval: TimeStamp()} using the Open Source
         // PowerBASIC to C++ converter found at https://github.com/uCalc/powerbasic-to-cpp

         {@Define:
            Var: Array As String
            Var: ArrayNames As Table
            Var: CleanUp As String
            Var: BitType As String
         }

Criteria: 2
Comment: Handles long line break (underscore _)
Pass: 1

Criteria: 3
Highlight: True
PassOnce: False
Find: {nl}{line} _ [{comment:".*"}] {nl}

Replace: {comment: ' {comment}{nl}}{nl}{line}{sp}

Criteria: 4
Highlight: True
SkipOver: True
Find: {"[\(\)]"} {@Note: These are ( and ); must be skipped}
Replace: [Skip over]

Criteria: 5
Highlight: True
SkipOver: True
Find: ' [{comment:".*"}]
Replace: [Skip over]

Criteria: 6
Comment: Processes some operators
Pass: 2

Criteria: 7
Comment: Bitwise AND and OR
Highlight: True
Find: ({expr%})
Replace: ({@Eval: Replace(Replace(Replace('{expr}', "And", "&"), "Or", "|"), "=", "==")})

Criteria: 8
Comment: Logical (short-circuit) AND
Highlight: True
Find: And
Replace: &&

Criteria: 9
Comment: Logical (short-circuit) OR
Highlight: True
Find: Or
Replace: ||

Criteria: 10
Highlight: True
Find: Xor
Replace: ^

Criteria: 11
Highlight: True
Find: Not
Replace: ~

Criteria: 12
Highlight: True
Find: IsFalse
Replace: !

Criteria: 13
Highlight: True
Find: <>
Replace: !=

Criteria: 14
Highlight: True
Find: =
Replace: ==

Criteria: 15
Highlight: True
SkipOver: True
Find: { {nl}[%]|Then|Else|For|: } {var:" *[a-z\@\.]+"}[({index})] =
Replace: [Skip over]

Criteria: 16
Highlight: True
SkipOver: True
Find: { { #If | #ElseIf } Not | ' [{comment:".*"}] }
Replace: [Skip over]

Criteria: 17
Highlight: True
SkipOver: True
Find: Macro {name}[({params})] =
Replace: [Skip over]

Criteria: 18
Highlight: True
PassOnce: False
Find: Sub {name:1} [Alias {alias}]([{args}])[{exp: Export}] [{comment:" *'.*"}] {nl}
         {code+}
      End Sub
Replace: {exp:extern "C" __declspec(dllexport)} void {exp:__stdcall} {name}({args}) { {comment}
            {code}
         !!ReleaseDynamicArrays!!}

Criteria: 19
Selected: True
Highlight: True
BackColor: DarkKhaki
PassOnce: False
Find: Function {name:1} [Alias {alias}]([{args}])[{exp: Export}] As {type}[{comment:" *'.*"}]{nl}
         {code+}
      End Function
Replace: {exp:extern "C" __declspec(dllexport)} {type} {exp:__stdcall} {name}({args}) { {comment}
            {code}
         !!ReleaseDynamicArrays!!}

Criteria: 20
Highlight: True
PassOnce: False
Find: Function PBMain [()] [As Long]
Replace: Function main() As Long

Criteria: 21
Highlight: True
PassOnce: False
Find: Declare Function {name:1} [Lib {lib}] [Alias {alias}]
      [([{args}])] As {type}
Replace: {lib: extern __declspec(dllimport)} {type} {name}({args})

Criteria: 22
Highlight: True
Find: Declare Sub {name:1} [Lib {lib}] [Alias {alias}]
      [([{args}])]
Replace: {lib: extern __declspec(dllimport)} void {name}({args})

Criteria: 23
Highlight: True
Find: Static
Replace: static dim

Criteria: 24
Comment:
Pass: 3

Criteria: 25
Highlight: True
SkipOver: True
Find: // [{comment:".*"}]
Replace: [Skip over]

Criteria: 26
Highlight: True
Find: ' [{comment:".*"}]
Replace: // {comment}

Criteria: 27
Highlight: True
BackColor: Green
PassOnce: False
Find: {nl}%{equate} = {value}
Replace: {nl}const int {equate} = {value};

Criteria: 28
Highlight: True
BackColor: Green
Find: %{equate}
Replace: {equate}

Criteria: 29
Highlight: True
BackColor: SandyBrown
Find: {"&h"}
Replace: 0x

Criteria: 30
Highlight: True
BackColor: Green
PassOnce: False
Find: If {cond} Then {statement} [Else {else}]{nl}
Replace: if ({cond}) {{statement}{nl}}{else: else {{else}{nl}}}{nl}

Criteria: 31
Highlight: True
PassOnce: False
Find: If {cond} Then {nl}
         {code+}
      End If
Replace: if ({cond}) {
            {code}
         }

Criteria: 32
Highlight: True
PassOnce: False
Find: For {x} = {start} To {stop} [Step {inc=1}]{nl}
         {code+}
      Next
Replace: for ({x}={start}; {x}{@Eval: IIF(sgn({inc})>0, '<', '>')}={stop}; {x} += {inc}) {
            {code}
         }

Criteria: 33
Highlight: True
PassOnce: False
Find: While {cond} {nl}
         {code+}
      Wend
Replace: while ({cond}) {
            {code}
         }

Criteria: 34
Highlight: True
PassOnce: False
Find: Do { {while: While} | {until: Until} } {cond} {nl}
         {code+}
      Loop
Replace: while ({while: {cond}}{until:!({cond})}) {
            {code}
         }

Criteria: 35
Highlight: True
PassOnce: False
Find: Do{nl}
         {code+}
      Loop { {while: While} | {until: Until} } {cond}
Replace: do {
            {code}
         } while ({while: {cond}}{until:!({cond})})

Criteria: 36
Highlight: True
Find: !!ReleaseDynamicArrays!!
Replace: {@Eval:
            CleanUp = ""
            uc_For(x, 1, Count(ArrayNames), 1,
               Array = ReadKey(ArrayNames, x)
               CleanUp=CleanUp+"delete[] "+Array+";{nl}"
               Delete(ArrayNames, Array)
            )
            CleanUp
         }

Criteria: 37
Highlight: True
BackColor: Orange
PassOnce: False
Find: { Dim | {member: {nl}}} {var:1} As {type} [{ptr: Ptr}]
Replace: {member: {nl}}{type} {ptr:*}{var};

Criteria: 38
Highlight: True
PassOnce: False
Find: {nl} {bitfield:1} As Bit * {size} [{in: In {type}}]
Replace: {nl}{@Eval:
            IIf("{type}" <> "", SetVar(BitType, "{type}"))
            BitType
         } {bitfield} : {size};

Criteria: 39
Highlight: True
PassOnce: False
Find: Dim {array}({subscript}) As {type}
Replace: {type} *{array} = new {type} [{subscript}+1];{@Eval:
            Insert(ArrayNames, "{array}")
         }{@Define:: Syntax: {array}({index}) ::= {array}[{index}]}

Criteria: 40
Highlight: True
BackColor: SandyBrown
PassOnce: False
Find: Dim {var1}, {more}
Replace: Dim {var1}
         Dim {more}

Criteria: 41
Highlight: True
BackColor: Gold
PassOnce: False
Find: { Local | Global | Register }
Replace: Dim

Criteria: 42
Highlight: True
Find: @
Replace: *

Criteria: 43
Highlight: True
BackColor: Silver
Find: VarPtr({var})
Replace: &{var}

Criteria: 44
Highlight: True
BackColor: DeepSkyBlue
PassOnce: False
Find: Function = {value}
Replace: return {value};

Criteria: 45
Highlight: True
Find: Exit Sub
Replace: return;

Criteria: 46
Highlight: True
BackColor: Lime
Find: Long
Replace: int

Criteria: 47
Highlight: True
BackColor: Red
Find: Single
Replace: float

Criteria: 48
Highlight: True
Find: { Double | Extended | Ext }
Replace: double{@Note: May instead use "long double" for Extended}

Criteria: 49
Highlight: True
Find: Byte
Replace: unsigned char

Criteria: 50
Highlight: True
Find: Integer
Replace: short

Criteria: 51
Highlight: True
Find: Word
Replace: unsigned short

Criteria: 52
Highlight: True
Find: Dword
Replace: unsigned int

Criteria: 53
Highlight: True
Find: Quad
Replace: long long

Criteria: 54
Find: AsciiZ
Replace: LPCSTR

Criteria: 55
Highlight: True
BackColor: SlateBlue
PassOnce: False
Find: ByRef {arg} As {type:1}
Replace: {type}& {arg}

Criteria: 56
Highlight: True
BackColor: Pink
PassOnce: False
Find: ByVal {arg} As {type:1} [{ptr: Ptr}]
Replace: {type} {ptr:*}{arg}

Criteria: 57
Highlight: True
PassOnce: False
Find: Type {name:1}
         {members+}
      End Type
Replace: struct {name} {
            {members}
         }

Criteria: 58
Highlight: True
Find: Macro {name} [{params: ({paramlist})}] = {replacement}
Replace: #define {name}{params} {replacement}

Criteria: 59
Highlight: True
Find: #If
Replace: #if

Criteria: 60
Highlight: True
Find: #Else
Replace: #else

Criteria: 61
Highlight: True
Find: #ElseIf
Replace: #elif

Criteria: 62
Highlight: True
Find: #EndIf
Replace: #endif

Criteria: 63
Highlight: True
PassOnce: False
Find: [{NOT: Not }] %Def({const})
Replace: {NOT:!}defined {const}

Criteria: 64
Comment: Adds semi-colons to statements
Pass: 4

Criteria: 65
Highlight: True
SkipOver: True
Find: { "{" | "}" | ; | //[{".*"}] | #{".*"} } {nl} [{"[ \n]+"}]
Replace: [Skip over]

Criteria: 66
Comment: Skips over so that colons in bit fields are not affected
Highlight: True
SkipOver: True
Find: struct {name:1} "{" {members+} "}"
Replace: [Skip over]

Criteria: 67
Highlight: True
Find: :
Replace: ;

Criteria: 68
Highlight: True
PassOnce: False
Find: {nl}
Replace: ;{nl}

# End Search

# pb-to-cpp.uc - uCalc Transformation file
# This file was saved with uCalc Transform 2.0 on 1/10/2014 2:52:37 PM
# Comment: Converts PB source code to C++; modified by Daniel Corbier

ExternalKeywords: Exclude, Comment, Selected, ParentChild, FindMode, OutputFile, BatchAction, SEND
ExternalKeywords: Highlight, ForeColor, BackColor, FontName, FontSize, FontStyle
ExternalKeywords: FilterEndText, FilterSeparator, FilterSort, FilterSortFunc, FilterStartText, FilterUnique

FindMode: Replace

# Definitions

# Search Criteria

Criteria: 0
Enabled: True
Exclude: False
Comment: Converts PB source code to C++; modified by Daniel Corbier
Selected: False
Highlight: False
ForeColor: ControlText
BackColor: Aqua
FontName:
FontSize:
FontStyle:
CaseSensitive: False
QuoteSensitive: True
CodeBlockSensitive: True
FilterEndText:
FilterSeparator: {#10}
FilterSort: False
FilterSortFunc:
FilterStartText:
FilterUnique: False
Min: 0
Max: -1
MinSoft: 0
MaxSoft: -1
BatchAction: Transform
OutputFile:
SEND:
StartAfter: 0
StopAfter: -1
SkipOver: False
ParentChild: 0
Pass: 0
PassOnce: True
Precedence: 0
RightToLeft: False

Criteria: 1
Highlight: True
PassOnce: False
Find: {@Start}
Replace: // This file ({@Eval: Extract(ShortName(InputFile), ".")}.cpp) was converted from {@Eval: Extract(ShortName(InputFile), ".")}.Bas
         // with {@Eval: AppName} on {@Eval: TimeStamp()} using the Open Source
         // PowerBASIC to C++ converter found at https://github.com/uCalc/powerbasic-to-cpp

         {@Define:
            Var: Array As String
            Var: ArrayNames As Table
            Var: CleanUp As String
            Var: BitType As String
         }

Criteria: 2
Comment: Handles long line break (underscore _)
Pass: 1

Criteria: 3
Highlight: True
PassOnce: False
Find: {nl}{line} _ [{comment:".*"}] {nl}

Replace: {comment: ' {comment}{nl}}{nl}{line}{sp}

Criteria: 4
Highlight: True
SkipOver: True
Find: {"[\(\)]"} {@Note: These are ( and ); must be skipped}
Replace: [Skip over]

Criteria: 5
Highlight: True
SkipOver: True
Find: ' [{comment:".*"}]
Replace: [Skip over]

Criteria: 6
Comment: Processes some operators
Pass: 2

Criteria: 7
Comment: Bitwise AND and OR
Highlight: True
Find: ({expr%})
Replace: ({@Eval: Replace(Replace(Replace('{expr}', "And", "&"), "Or", "|"), "=", "==")})

Criteria: 8
Comment: Logical (short-circuit) AND
Highlight: True
Find: And
Replace: &&

Criteria: 9
Comment: Logical (short-circuit) OR
Highlight: True
Find: Or
Replace: ||

Criteria: 10
Highlight: True
Find: Xor
Replace: ^

Criteria: 11
Highlight: True
Find: Not
Replace: ~

Criteria: 12
Highlight: True
Find: IsFalse
Replace: !

Criteria: 13
Highlight: True
Find: <>
Replace: !=

Criteria: 14
Highlight: True
Find: =
Replace: ==

Criteria: 15
Highlight: True
SkipOver: True
Find: { {nl}[%]|Then|Else|For|: } {var:" *[a-z\@\.]+"}[({index})] =
Replace: [Skip over]

Criteria: 16
Highlight: True
SkipOver: True
Find: { { #If | #ElseIf } Not | ' [{comment:".*"}] }
Replace: [Skip over]

Criteria: 17
Highlight: True
SkipOver: True
Find: Macro {name}[({params})] =
Replace: [Skip over]

Criteria: 18
Highlight: True
PassOnce: False
Find: Sub {name}([{args}]) [{comment:" *'.*"}] {nl}
         {code+}
      End Sub
Replace: void {name}({args}) { {comment}
            {code}
         !!ReleaseDynamicArrays!!}

Criteria: 19
Highlight: True
BackColor: DarkKhaki
PassOnce: False
Find: Function {name}([{args}]) As {type}[{comment:" *'.*"}]{nl}
         {code+}
      End Function
Replace: {type} {name}({args}) { {comment}
            {code}
         !!ReleaseDynamicArrays!!}

Criteria: 20
Highlight: True
PassOnce: False
Find: Function PBMain [()] [As Long]
Replace: Function main() As Long

Criteria: 21
Highlight: True
PassOnce: False
Find: Declare Function {name:1} [Lib {lib}] [Alias {alias}]
      [([{args}])] As {type}
Replace: {lib: extern __declspec(dllimport)} {type} {name}({args})

Criteria: 22
Highlight: True
Find: Declare Sub {name:1} [Lib {lib}] [Alias {alias}]
      [([{args}])]
Replace: {lib: extern __declspec(dllimport)} void {name}({args})

Criteria: 23
Highlight: True
Find: Static
Replace: static dim

Criteria: 24
Comment:
Pass: 3

Criteria: 25
Highlight: True
SkipOver: True
Find: // [{comment:".*"}]
Replace: [Skip over]

Criteria: 26
Highlight: True
Find: ' [{comment:".*"}]
Replace: // {comment}

Criteria: 27
Highlight: True
BackColor: Green
PassOnce: False
Find: {nl}%{equate} = {value}
Replace: {nl}const int {equate} = {value};

Criteria: 28
Highlight: True
BackColor: Green
Find: %{equate}
Replace: {equate}

Criteria: 29
Highlight: True
BackColor: SandyBrown
Find: {"&h"}
Replace: 0x

Criteria: 30
Highlight: True
BackColor: Green
PassOnce: False
Find: If {cond} Then {statement} [Else {else}]{nl}
Replace: if ({cond}) {{statement}{nl}}{else: else {{else}{nl}}}{nl}

Criteria: 31
Highlight: True
PassOnce: False
Find: If {cond} Then {nl}
         {code+}
      End If
Replace: if ({cond}) {
            {code}
         }

Criteria: 32
Highlight: True
PassOnce: False
Find: For {x} = {start} To {stop} [Step {inc=1}]{nl}
         {code+}
      Next
Replace: for ({x}={start}; {x}{@Eval: IIF(sgn({inc})>0, '<', '>')}={stop}; {x} += {inc}) {
            {code}
         }

Criteria: 33
Highlight: True
PassOnce: False
Find: While {cond} {nl}
         {code+}
      Wend
Replace: while ({cond}) {
            {code}
         }

Criteria: 34
Highlight: True
PassOnce: False
Find: Do { {while: While} | {until: Until} } {cond} {nl}
         {code+}
      Loop
Replace: while ({while: {cond}}{until:!({cond})}) {
            {code}
         }

Criteria: 35
Highlight: True
PassOnce: False
Find: Do{nl}
         {code+}
      Loop { {while: While} | {until: Until} } {cond}
Replace: do {
            {code}
         } while ({while: {cond}}{until:!({cond})})

Criteria: 36
Highlight: True
Find: !!ReleaseDynamicArrays!!
Replace: {@Eval:
            CleanUp = ""
            uc_For(x, 1, Count(ArrayNames), 1,
               Array = ReadKey(ArrayNames, x)
               CleanUp=CleanUp+"delete[] "+Array+";{nl}"
               Delete(ArrayNames, Array)
            )
            CleanUp
         }

Criteria: 37
Highlight: True
BackColor: Orange
PassOnce: False
Find: { Dim | {member: {nl}}} {var:1} As {type} [{ptr: Ptr}]
Replace: {member: {nl}}{type} {ptr:*}{var};

Criteria: 38
Highlight: True
PassOnce: False
Find: {nl} {bitfield:1} As Bit * {size} [{in: In {type}}]
Replace: {nl}{@Eval:
            IIf("{type}" <> "", SetVar(BitType, "{type}"))
            BitType
         } {bitfield} : {size};

Criteria: 39
Highlight: True
PassOnce: False
Find: Dim {array}({subscript}) As {type}
Replace: {type} *{array} = new {type} [{subscript}+1];{@Eval:
            Insert(ArrayNames, "{array}")
         }{@Define:: Syntax: {array}({index}) ::= {array}[{index}]}

Criteria: 40
Highlight: True
BackColor: SandyBrown
PassOnce: False
Find: Dim {var1}, {more}
Replace: Dim {var1}
         Dim {more}

Criteria: 41
Highlight: True
BackColor: Gold
PassOnce: False
Find: { Local | Global | Register }
Replace: Dim

Criteria: 42
Highlight: True
Find: @
Replace: *

Criteria: 43
Highlight: True
BackColor: Silver
Find: VarPtr({var})
Replace: &{var}

Criteria: 44
Highlight: True
BackColor: DeepSkyBlue
PassOnce: False
Find: Function = {value}
Replace: return {value};

Criteria: 45
Highlight: True
Find: Exit Sub
Replace: return;

Criteria: 46
Highlight: True
BackColor: Lime
Find: Long
Replace: int

Criteria: 47
Highlight: True
BackColor: Red
Find: Single
Replace: float

Criteria: 48
Highlight: True
Find: { Double | Extended | Ext }
Replace: double{@Note: May instead use "long double" for Extended}

Criteria: 49
Highlight: True
Find: Byte
Replace: unsigned char

Criteria: 50
Highlight: True
Find: Integer
Replace: short

Criteria: 51
Highlight: True
Find: Word
Replace: unsigned short

Criteria: 52
Highlight: True
Find: Dword
Replace: unsigned int

Criteria: 53
Highlight: True
Find: Quad
Replace: long long

Criteria: 54
Selected: True
Find: AsciiZ
Replace: LPCSTR

Criteria: 55
Highlight: True
BackColor: SlateBlue
PassOnce: False
Find: ByRef {arg} As {type:1}
Replace: {type}& {arg}

Criteria: 56
Highlight: True
BackColor: Pink
PassOnce: False
Find: ByVal {arg} As {type:1} [{ptr: Ptr}]
Replace: {type} {ptr:*}{arg}

Criteria: 57
Highlight: True
PassOnce: False
Find: Type {name:1}
         {members+}
      End Type
Replace: struct {name} {
            {members}
         }

Criteria: 58
Highlight: True
Find: Macro {name} [{params: ({paramlist})}] = {replacement}
Replace: #define {name}{params} {replacement}

Criteria: 59
Highlight: True
Find: #If
Replace: #if

Criteria: 60
Highlight: True
Find: #Else
Replace: #else

Criteria: 61
Highlight: True
Find: #ElseIf
Replace: #elif

Criteria: 62
Highlight: True
Find: #EndIf
Replace: #endif

Criteria: 63
Highlight: True
PassOnce: False
Find: [{NOT: Not }] %Def({const})
Replace: {NOT:!}defined {const}

Criteria: 64
Comment: Adds semi-colons to statements
Pass: 4

Criteria: 65
Highlight: True
SkipOver: True
Find: { "{" | "}" | ; | //[{".*"}] | #{".*"} } {nl} [{"[ \n]+"}]
Replace: [Skip over]

Criteria: 66
Comment: Skips over so that colons in bit fields are not affected
Highlight: True
SkipOver: True
Find: struct {name:1} "{" {members+} "}"
Replace: [Skip over]

Criteria: 67
Highlight: True
Find: :
Replace: ;

Criteria: 68
Highlight: True
PassOnce: False
Find: {nl}
Replace: ;{nl}

# End Search


Update strings.uc

Update strings.uc

# strings.uc - uCalc Transformation file
# This file was saved with uCalc Transform 2.96 on 6/24/2014 7:25:33 PM
# Comment: Converts string-related code from PowerBASIC to C++

ExternalKeywords: Exclude, Comment, Selected, ParentChild, FindMode, InputFile, OutputFile, BatchAction, SEND
ExternalKeywords: Highlight, ForeColor, BackColor, FontName, FontSize, FontStyle
ExternalKeywords: FilterEndText, FilterSeparator, FilterSort, FilterSortFunc, FilterStartText, FilterUnique, FilterTally

FindMode: Replace

# Definitions

# Search Criteria

Criteria: 0
Enabled: True
Exclude: False
Comment: Converts string-related code from PowerBASIC to C++
Selected: False
Highlight: True
ForeColor: ControlText
BackColor: Aqua
FontName:
FontSize:
FontStyle:
CaseSensitive: False
QuoteSensitive: True
CodeBlockSensitive: True
FilterEndText:
FilterSeparator: {#10}
FilterSort: False
FilterSortFunc:
FilterStartText:
FilterUnique: False
FilterTally: False
Min: 0
Max: -1
MinSoft: 0
MaxSoft: -1
BatchAction: Transform
InputFile:
OutputFile:
SEND:
StartAfter: 0
StopAfter: -1
SkipOver: False
ParentChild: 0
Pass: 0
PassOnce: True
Precedence: 0
RightToLeft: False

Criteria: 1
Find:
Replace: {@Token:: : ~~ Properties: ucStatementSep }

Criteria: 2
Comment:
Pass: 1

Criteria: 3
SkipOver: True
Find: ' {Comment:".*"}
Replace: [Skip over]

Criteria: 4
Find: String
Replace: string

Criteria: 5
Find: WString
Replace: wstring

Criteria: 6
BackColor: DodgerBlue
PassOnce: False
Find: InStr([{n=1},] {MainStr}, {MatchStr})
Replace: PB_INSTR({n}, {MainStr}, {MatchStr})

Criteria: 7
BackColor: Tomato
PassOnce: False
Find: InStr([{n=1},] {MainStr}, Any {MatchStr})
Replace: PB_INSTR_ANY({n}, {MainStr}, {MatchStr})

Criteria: 8
BackColor: Red
PassOnce: False
Find: Left$({Str}, {n})
Replace: PB_LEFT({Str}, {n})

Criteria: 9
BackColor: Brown
PassOnce: False
Find: Right$({Str}, {n})
Replace: PB_RIGHT({Str}, {n})

Criteria: 10
BackColor: Orange
PassOnce: False
Find: Mid$({Str}, {Start} [, {Length=FULL_STRING}])
Replace: PB_MID({Str}, {Start}, {Length})

Criteria: 11
BackColor: DeepSkyBlue
PassOnce: False
Find: {Sep: {nl} | : | Then | Else }
      Mid$({Str}, {Start} [, {Length=FULL_STRING}]) = {Txt%}
Replace: {Sep} PB_MID_REPLACE({Str}, {Txt}, {Start}, {Length})

Criteria: 12
BackColor: RoyalBlue
PassOnce: False
Find: Val({Str})
Replace: stold({Str})

Criteria: 13
Find: Space$({count})
Replace: string({count}, _char( ))

Criteria: 14
Find: String$({count}, {char})
Replace: string({count}, {char})

Criteria: 15
BackColor: Violet
Find: String$({count}, {q}{char}{q})
Replace: string({count}, _char({char}))

Criteria: 16
Find: {function: UCase | LCase | Hex | Oct | Str}$({arg})
Replace: PB_{@Eval: UCase("{function}", "{'.*'}")}({arg})

Criteria: 17
Find: Trim$({Str} [, ANY {chars=" "}])
Replace: PB_TRIM_ANY({Str}, {chars})

Criteria: 18
BackColor: Gold
Find: LTrim$({Str} [, ANY {chars=" "}])
Replace: PB_LTRIM_ANY({Str}, {chars})

Criteria: 19
BackColor: Silver
Find: RTrim$({Str} [, ANY {chars=" "}])
Replace: PB_RTRIM_ANY({Str}, {chars})

Criteria: 20
Find: Extract$([{start=1},] {MainStr}, {MatchStr})
Replace: PB_EXTRACT({start}, {MainStr}, {MatchStr})

Criteria: 21
Find: Extract$([{start=1},] {MainStr}, Any {MatchStr})
Replace: PB_EXTRACT_ANY({start}, {MainStr}, {MatchStr})

Criteria: 22
Find: Remain$([{start=1},] {MainStr}, {MatchStr})
Replace: PB_REMAIN({start}, {MainStr}, {MatchStr})

Criteria: 23
Find: Remain$([{start=1},] {MainStr}, Any {MatchStr})
Replace: PB_REMAIN_ANY({start}, {MainStr}, {MatchStr})

Criteria: 24
Find: Remove$({Str}, {MatchStr})
Replace: PB_REMOVE({Str}, {MatchStr})

Criteria: 25
Find: Remove$({Str}, Any {MatchStr})
Replace: PB_REMOVE_ANY({Str}, {MatchStr})

Criteria: 26
Find: Replace {MatchStr} With {NewStr} In {MainStr}
Replace: PB_REPLACE({MainStr}, {MatchStr}, {NewStr})

Criteria: 27
Find: Replace Any {MatchStr} With {NewStr} In {MainStr}
Replace: PB_REPLACE_ANY({MainStr}, {MatchStr}, {NewStr})

Criteria: 28
Find: Repeat$({count}, {Str})
Replace: PB_REPEAT({Str}, {count})

Criteria: 29
Find: Asc({Str}[, {pos=1}])
Replace: PB_ASC({Str}, {pos})

Criteria: 30
Find: ${equate: NUL|BEL|BS|TAB|LF|VT|FF|CR|CRLF|EOF|ESC|SPC|DQ|SQ|QCQ }
Replace: PB_{@Eval: UCase("{equate}", "{'.*'}")}

Criteria: 31
Find: Peek$({args})
Replace: PB_PEEK_STR((DWord){args})

Criteria: 32
Find: Poke$ {address}, {stringexpr}
Replace: PB_POKE_STR((DWord){address}, {stringexpr})

Criteria: 33
Selected: True
Find: RegExpr {mask} In {main} [At {start=1}] To {iPos} [, {iLen=discard}]
Replace: PB_REGEXPR({mask}, {main}, {start}, {iPos}, {iLen})

Criteria: 34
Comment: Doubles backslash to avoid escape
Pass: 2

Criteria: 35
Find: {q}{text$}{q}
Replace: {q}{@Evaluate: Replace({text}, "\", "\\")}{q}

# End Search

# strings.uc - uCalc Transformation file
# This file was saved with uCalc Transform 2.96 on 6/23/2014 6:18:47 PM
# Comment: Converts string-related code from PowerBASIC to C++

ExternalKeywords: Exclude, Comment, Selected, ParentChild, FindMode, InputFile, OutputFile, BatchAction, SEND
ExternalKeywords: Highlight, ForeColor, BackColor, FontName, FontSize, FontStyle
ExternalKeywords: FilterEndText, FilterSeparator, FilterSort, FilterSortFunc, FilterStartText, FilterUnique, FilterTally

FindMode: Replace

# Definitions

# Search Criteria

Criteria: 0
Enabled: True
Exclude: False
Comment: Converts string-related code from PowerBASIC to C++
Selected: False
Highlight: True
ForeColor: ControlText
BackColor: Aqua
FontName:
FontSize:
FontStyle:
CaseSensitive: False
QuoteSensitive: True
CodeBlockSensitive: True
FilterEndText:
FilterSeparator: {#10}
FilterSort: False
FilterSortFunc:
FilterStartText:
FilterUnique: False
FilterTally: False
Min: 0
Max: -1
MinSoft: 0
MaxSoft: -1
BatchAction: Transform
InputFile:
OutputFile:
SEND:
StartAfter: 0
StopAfter: -1
SkipOver: False
ParentChild: 0
Pass: 0
PassOnce: True
Precedence: 0
RightToLeft: False

Criteria: 1
Find:
Replace: {@Token:: : ~~ Properties: ucStatementSep }

Criteria: 2
Comment:
Pass: 1

Criteria: 3
SkipOver: True
Find: ' {Comment:".*"}
Replace: [Skip over]

Criteria: 4
Find: String
Replace: string

Criteria: 5
Find: WString
Replace: wstring

Criteria: 6
BackColor: DodgerBlue
PassOnce: False
Find: InStr([{n=1},] {MainStr}, {MatchStr})
Replace: PB_INSTR({n}, {MainStr}, {MatchStr})

Criteria: 7
BackColor: Tomato
PassOnce: False
Find: InStr([{n=1},] {MainStr}, Any {MatchStr})
Replace: PB_INSTR_ANY({n}, {MainStr}, {MatchStr})

Criteria: 8
BackColor: Red
PassOnce: False
Find: Left$({Str}, {n})
Replace: PB_LEFT({Str}, {n})

Criteria: 9
BackColor: Brown
PassOnce: False
Find: Right$({Str}, {n})
Replace: PB_RIGHT({Str}, {n})

Criteria: 10
BackColor: Orange
PassOnce: False
Find: Mid$({Str}, {Start} [, {Length=FULL_STRING}])
Replace: PB_MID({Str}, {Start}, {Length})

Criteria: 11
BackColor: DeepSkyBlue
PassOnce: False
Find: {Sep: {nl} | : | Then | Else }
      Mid$({Str}, {Start} [, {Length=FULL_STRING}]) = {Txt%}
Replace: {Sep} PB_MID_REPLACE({Str}, {Txt}, {Start}, {Length})

Criteria: 12
BackColor: RoyalBlue
PassOnce: False
Find: Val({Str})
Replace: stold({Str})

Criteria: 13
Find: Space$({count})
Replace: string({count}, _char( ))

Criteria: 14
Find: String$({count}, {char})
Replace: string({count}, {char})

Criteria: 15
BackColor: Violet
Find: String$({count}, {q}{char}{q})
Replace: string({count}, _char({char}))

Criteria: 16
Find: {function: UCase | LCase | Hex | Oct | Str}$({arg})
Replace: PB_{@Eval: UCase("{function}", "{'.*'}")}({arg})

Criteria: 17
Find: Trim$({Str} [, ANY {chars=" "}])
Replace: PB_TRIM_ANY({Str}, {chars})

Criteria: 18
BackColor: Gold
Find: LTrim$({Str} [, ANY {chars=" "}])
Replace: PB_LTRIM_ANY({Str}, {chars})

Criteria: 19
BackColor: Silver
Find: RTrim$({Str} [, ANY {chars=" "}])
Replace: PB_RTRIM_ANY({Str}, {chars})

Criteria: 20
Find: Extract$([{start=1},] {MainStr}, {MatchStr})
Replace: PB_EXTRACT({start}, {MainStr}, {MatchStr})

Criteria: 21
Find: Extract$([{start=1},] {MainStr}, Any {MatchStr})
Replace: PB_EXTRACT_ANY({start}, {MainStr}, {MatchStr})

Criteria: 22
Find: Remain$([{start=1},] {MainStr}, {MatchStr})
Replace: PB_REMAIN({start}, {MainStr}, {MatchStr})

Criteria: 23
Find: Remain$([{start=1},] {MainStr}, Any {MatchStr})
Replace: PB_REMAIN_ANY({start}, {MainStr}, {MatchStr})

Criteria: 24
Find: Remove$({Str}, {MatchStr})
Replace: PB_REMOVE({Str}, {MatchStr})

Criteria: 25
Find: Remove$({Str}, Any {MatchStr})
Replace: PB_REMOVE_ANY({Str}, {MatchStr})

Criteria: 26
Find: Replace {MatchStr} With {NewStr} In {MainStr}
Replace: PB_REPLACE({MainStr}, {MatchStr}, {NewStr})

Criteria: 27
Find: Replace Any {MatchStr} With {NewStr} In {MainStr}
Replace: PB_REPLACE_ANY({MainStr}, {MatchStr}, {NewStr})

Criteria: 28
Find: Repeat$({count}, {Str})
Replace: PB_REPEAT({Str}, {count})

Criteria: 29
Find: Asc({Str}[, {pos=1}])
Replace: PB_ASC({Str}, {pos})

Criteria: 30
Find: ${equate: NUL|BEL|BS|TAB|LF|VT|FF|CR|CRLF|EOF|ESC|SPC|DQ|SQ|QCQ }
Replace: PB_{@Eval: UCase("{equate}", "{'.*'}")}

Criteria: 31
Find: Peek$({args})
Replace: PB_PEEK_STR((DWord){args})

Criteria: 32
Find: Poke$ {address}, {stringexpr}
Replace: PB_POKE_STR((DWord){address}, {stringexpr})

Criteria: 33
Selected: True
Find: RegExpr {mask} In {main} [At {start=1}] To {iPos}
Replace: PB_REGEXPR({mask}, {main}, {start}, {iPos})

Criteria: 34
Comment: Doubles backslash to avoid escape
Pass: 2

Criteria: 35
Find: {q}{text$}{q}
Replace: {q}{@Evaluate: Replace({text}, "\", "\\")}{q}

# End Search


fixed None access error after end-of-game


Removed all protectedwrites

Removed all protectedwrites

//---------------------------------------------------------------------------------------
//  *********   FIRAXIS SOURCE CODE   ******************
//  FILE:    XComGameState_EvacZone.uc
//  AUTHOR:  Josh Bouscher, David Burchanowski
//  PURPOSE: Game state object to track the evac zone
//---------------------------------------------------------------------------------------
//  Copyright (c) 2016 Firaxis Games, Inc. All rights reserved.
//---------------------------------------------------------------------------------------

class XComGameState_EvacZone extends XComGameState_BaseObject
	implements(X2VisualizedInterface)
	native(Core)
	config(GameCore);

var private const config string DefaultEvacBlueprintMapName;

var() const config int PositiveXDimension;
var() const config int PositiveYDimension;
var() const config int NegativeXDimension;
var() const config int NegativeYDimension;

// Start Issue #702
var() TTile CenterLocation;
var() ETeam Team;
// End Issue #702

// if specified, will use this map instead of the default evac zone map
var() string BlueprintMapOverride;

var() bool bMissionPlaced;

static function XComGameState_EvacZone PlaceEvacZone(XComGameState NewGameState, Vector SpawnLocation, optional ETeam InTeam = eTeam_XCom, optional string InBlueprintMapOverride)
{
	local XComWorldData WorldData;

	local XComGameState_EvacZone EvacState;
	local TTile SpawnTile;

	WorldData = `XWORLD;

	EvacState = GetEvacZone(InTeam);
	if (EvacState == none)
	{
		EvacState = XComGameState_EvacZone(NewGameState.CreateNewStateObject(class'XComGameState_EvacZone'));
		EvacState.Team = InTeam;
	}
	else
	{
		EvacState = XComGameState_EvacZone(NewGameState.ModifyStateObject(EvacState.Class, EvacState.ObjectID));
	}

	// try to put the zone squarely on the floor. If that isn't possible, then as a backup put it
	// at the requested location
	if(!WorldData.GetFloorTileForPosition(SpawnLocation, SpawnTile))
	{
		`Redscreen("PlaceEvacZone(): Unable to place evac zone on the floor. It may not function");
		SpawnTile = WorldData.GetTileCoordinatesFromPosition(SpawnLocation);
	}

	EvacState.BlueprintMapOverride = InBlueprintMapOverride;
	EvacState.SetCenterLocation(SpawnTile);

	class'XComGameState_BattleData'.static.SetGlobalAbilityEnabled('Evac', true, NewGameState);

	`XEVENTMGR.TriggerEvent('EvacZonePlaced', EvacState, EvacState, NewGameState);

	return EvacState;
}

static function XComGameState_EvacZone GetEvacZone(optional ETeam InTeam = eTeam_XCom)
{
	local XComGameState_EvacZone EvacState;
	local XComGameStateHistory History;

	History = `XCOMHISTORY;
	foreach History.IterateByClassType(class'XComGameState_EvacZone', EvacState)
	{
		if(EvacState.Team == InTeam)
		{
			return EvacState;
		}
	}

	return none;
}

function string GetEvacZoneBlueprintMap()
{
	return BlueprintMapOverride != "" ? BlueprintMapOverride : DefaultEvacBlueprintMapName;
}

function SetCenterLocation(TTile TileLoc)
{
	CenterLocation = TileLoc;
}

static function GetEvacMinMax2D( TTile CenterLoc, out TTile TileMin, out TTile TileMax )
{
	TileMin = CenterLoc;
	TileMax = CenterLoc;

	TileMin.X -= default.NegativeXDimension;
	TileMin.Y -= default.NegativeYDimension;

	TileMax.X += default.PositiveXDimension;
	TileMax.Y += default.PositiveYDimension;
}

static function GetEvacMinMax( TTile CenterLoc, out TTile TileMin, out TTile TileMax )
{
	GetEvacMinMax2D( CenterLoc, TileMin, TileMax );

	if (TileMin.Z > 0)
	{
		TileMin.Z--;
	}

	if (TileMax.Z < `XWORLD.NumZ - 1)
	{
		TileMax.Z++;
	}
}

function bool IsUnitInEvacZone(XComGameState_Unit UnitState)
{
	local TTile UnitTile, Min, Max;
	local int IsOnFloor;

	GetEvacMinMax( CenterLocation, Min, Max );
	UnitTile = UnitState.TileLocation;

	if (UnitTile.X < Min.X || UnitTile.X > Max.X)
		return false;
	if (UnitTile.Y < Min.Y || UnitTile.Y > Max.Y)
		return false;
	if (UnitTile.Z < Min.Z || UnitTile.Z > Max.Z)
		return false;

	if (!class'X2TargetingMethod_EvacZone'.static.ValidateEvacTile( UnitTile, IsOnFloor, bMissionPlaced ))
	{
		return false;
	}

	return true;
}

function Actor FindOrCreateVisualizer( optional XComGameState Gamestate = none )
{
	local X2Actor_EvacZone EvacZoneActor;

	// if the evac zone already exists. Destroy it. We need to rebuild the blueprint
	EvacZoneActor = X2Actor_EvacZone( GetVisualizer( ) );
	if (EvacZoneActor != none)
	{
		EvacZoneActor.Destroy( );
	}

	EvacZoneActor = `BATTLE.Spawn( class'X2Actor_EvacZone' );
	`XCOMHISTORY.SetVisualizer( ObjectID, EvacZoneActor );

	return EvacZoneActor;
}

function SyncVisualizer(optional XComGameState GameState = none)
{
	local X2Actor_EvacZone EvacZoneActor;

	EvacZoneActor = X2Actor_EvacZone( GetVisualizer( ) );

	// and then init it
	EvacZoneActor.InitEvacZone(self);
}

function AppendAdditionalSyncActions( out VisualizationActionMetadata ActionMetadata, const XComGameStateContext Context)
{
}

DefaultProperties
{
	bTacticalTransient=true
	Team=eTeam_XCom
}

//---------------------------------------------------------------------------------------
//  *********   FIRAXIS SOURCE CODE   ******************
//  FILE:    XComGameState_EvacZone.uc
//  AUTHOR:  Josh Bouscher, David Burchanowski
//  PURPOSE: Game state object to track the evac zone
//---------------------------------------------------------------------------------------
//  Copyright (c) 2016 Firaxis Games, Inc. All rights reserved.
//---------------------------------------------------------------------------------------

class XComGameState_EvacZone extends XComGameState_BaseObject
	implements(X2VisualizedInterface)
	native(Core)
	config(GameCore);

var private const config string DefaultEvacBlueprintMapName;

var() const config int PositiveXDimension;
var() const config int PositiveYDimension;
var() const config int NegativeXDimension;
var() const config int NegativeYDimension;

var() protectedwrite TTile CenterLocation;
var() protectedwrite ETeam Team;

// if specified, will use this map instead of the default evac zone map
var() string BlueprintMapOverride;

var() bool bMissionPlaced;

static function XComGameState_EvacZone PlaceEvacZone(XComGameState NewGameState, Vector SpawnLocation, optional ETeam InTeam = eTeam_XCom, optional string InBlueprintMapOverride)
{
	local XComWorldData WorldData;

	local XComGameState_EvacZone EvacState;
	local TTile SpawnTile;

	WorldData = `XWORLD;

	EvacState = GetEvacZone(InTeam);
	if (EvacState == none)
	{
		EvacState = XComGameState_EvacZone(NewGameState.CreateNewStateObject(class'XComGameState_EvacZone'));
		EvacState.Team = InTeam;
	}
	else
	{
		EvacState = XComGameState_EvacZone(NewGameState.ModifyStateObject(EvacState.Class, EvacState.ObjectID));
	}

	// try to put the zone squarely on the floor. If that isn't possible, then as a backup put it
	// at the requested location
	if(!WorldData.GetFloorTileForPosition(SpawnLocation, SpawnTile))
	{
		`Redscreen("PlaceEvacZone(): Unable to place evac zone on the floor. It may not function");
		SpawnTile = WorldData.GetTileCoordinatesFromPosition(SpawnLocation);
	}

	EvacState.BlueprintMapOverride = InBlueprintMapOverride;
	EvacState.SetCenterLocation(SpawnTile);

	class'XComGameState_BattleData'.static.SetGlobalAbilityEnabled('Evac', true, NewGameState);

	`XEVENTMGR.TriggerEvent('EvacZonePlaced', EvacState, EvacState, NewGameState);

	return EvacState;
}

static function XComGameState_EvacZone GetEvacZone(optional ETeam InTeam = eTeam_XCom)
{
	local XComGameState_EvacZone EvacState;
	local XComGameStateHistory History;

	History = `XCOMHISTORY;
	foreach History.IterateByClassType(class'XComGameState_EvacZone', EvacState)
	{
		if(EvacState.Team == InTeam)
		{
			return EvacState;
		}
	}

	return none;
}

function string GetEvacZoneBlueprintMap()
{
	return BlueprintMapOverride != "" ? BlueprintMapOverride : DefaultEvacBlueprintMapName;
}

function SetCenterLocation(TTile TileLoc)
{
	CenterLocation = TileLoc;
}

static function GetEvacMinMax2D( TTile CenterLoc, out TTile TileMin, out TTile TileMax )
{
	TileMin = CenterLoc;
	TileMax = CenterLoc;

	TileMin.X -= default.NegativeXDimension;
	TileMin.Y -= default.NegativeYDimension;

	TileMax.X += default.PositiveXDimension;
	TileMax.Y += default.PositiveYDimension;
}

static function GetEvacMinMax( TTile CenterLoc, out TTile TileMin, out TTile TileMax )
{
	GetEvacMinMax2D( CenterLoc, TileMin, TileMax );

	if (TileMin.Z > 0)
	{
		TileMin.Z--;
	}

	if (TileMax.Z < `XWORLD.NumZ - 1)
	{
		TileMax.Z++;
	}
}

function bool IsUnitInEvacZone(XComGameState_Unit UnitState)
{
	local TTile UnitTile, Min, Max;
	local int IsOnFloor;

	GetEvacMinMax( CenterLocation, Min, Max );
	UnitTile = UnitState.TileLocation;

	if (UnitTile.X < Min.X || UnitTile.X > Max.X)
		return false;
	if (UnitTile.Y < Min.Y || UnitTile.Y > Max.Y)
		return false;
	if (UnitTile.Z < Min.Z || UnitTile.Z > Max.Z)
		return false;

	if (!class'X2TargetingMethod_EvacZone'.static.ValidateEvacTile( UnitTile, IsOnFloor, bMissionPlaced ))
	{
		return false;
	}

	return true;
}

function Actor FindOrCreateVisualizer( optional XComGameState Gamestate = none )
{
	local X2Actor_EvacZone EvacZoneActor;

	// if the evac zone already exists. Destroy it. We need to rebuild the blueprint
	EvacZoneActor = X2Actor_EvacZone( GetVisualizer( ) );
	if (EvacZoneActor != none)
	{
		EvacZoneActor.Destroy( );
	}

	EvacZoneActor = `BATTLE.Spawn( class'X2Actor_EvacZone' );
	`XCOMHISTORY.SetVisualizer( ObjectID, EvacZoneActor );

	return EvacZoneActor;
}

function SyncVisualizer(optional XComGameState GameState = none)
{
	local X2Actor_EvacZone EvacZoneActor;

	EvacZoneActor = X2Actor_EvacZone( GetVisualizer( ) );

	// and then init it
	EvacZoneActor.InitEvacZone(self);
}

function AppendAdditionalSyncActions( out VisualizationActionMetadata ActionMetadata, const XComGameStateContext Context)
{
}

DefaultProperties
{
	bTacticalTransient=true
	Team=eTeam_XCom
}


Add X2AIBTBehaviorTree.uc to the highlander (#410)

class X2AIBTBehaviorTree extends Object
	native(AI)
	config(AI);

var private config array<BehaviorTreeNode> Behaviors;
var private native Map_Mirror BehaviorsMap{TMap<FName, INT>};  //  maps table name to index into Behaviors array

var config array<EquivalentAbilityNames> EquivalentAbilities;
var int ActiveObjectID; // Currently-running behavior tree on this unit.  Clears this value when done.
/*
EquivalentAbilities is configured alongside the BehaviorTree nodes, and maps any equivalent ability names to a key ability name.
Allows for reuse of a BT node using an ability KeyName without having to duplicate the same nodes for other equivalent abilities.
i.e.
 EquivalentAbilities sample config entry:
 EquivalentAbilities=( KeyName=StandardShot, EquivalentAbilityName[0]=AssaultRifleStandardShot,
											EquivalentAbilityName[1]=ShotgunStandardShot,
											EquivalentAbilityName[2]=CannonStandardShot,
											EquivalentAbilityName[3]=SniperStandardFire )

Hence, nodes like the following can be generalized for any 'StandardShot' types and can be used for all unit with any standard-shot weapon:
 Behaviors=(BehaviorName=ShootIfAvailable, NodeType=Sequence, Child[0]=IsAbilityAvailable-StandardShot, Child[1]=HasAmmo, Child[2]=SelectTargetForStandardShot, Child[3]=SelectAbility-StandardShot)
*/

//  Behavior Tree Queue data.
var int ActiveQueueID;  // ID of the unit from the ActiveBTQueue that is set to run a specified behavior tree N times.
var bool bBTQueueTimerActive; // Set when there is an active timer running to process the entries in the BT queue.

// Flag to wait on an EndMove event trigger before proceeding with the behavior tree run.
var bool bWaitingOnEndMoveEvent;
var bool bWaitingOnSquadConcealment; // Wait for enemy squad concealment to break before proceeding with BT run.

struct native BTQueueEntry		// Struct for keeping track of units whose behavior tree are queued up to run.
{
	var int ObjectID;			// ID of unit whose Behavior Tree is queued up.
	var int RunCount;			// Number of times to run this unit's behavior tree, in a row.
	var int HistoryIndex;		// Minimum history index to wait before kicking off behavior tree.
	var Name Node;				// Name of root node of behavior tree to run.
	var bool bSurprisedScamper; // True if this unit should run behavior tree with the SurprisedScamper condition.
	var bool bFirstScamper;		// True if this unit is the first in its group to scamper. Used to set the AI begin reveal.
	var bool bScamperEntry;		// True if this entry is for a scamper action.
	var bool bInitFromPlayerEachRun; // True to reset more behavior tree vars on each run as if it is the start of the player turn. BTVars & ErrorChecking.
	var bool bInitiatedFromEffect; // True if this entry was initiated by an X2Effect_RunBehaviorTree.
};
var array<BTQueueEntry> ActiveBTQueue;  // List of behavior trees to kick off.
var BTQueueEntry ActiveBTQueueEntry; //Currently active behavior tree queue entry

// Debug data
var X2AIBTBehavior ActiveNode;
var String ActiveCharacterName;
var Name LastRunningNodeName;

struct native BTCustomNodeEntry
{
	var Name NodeTypeString;
	var Name ExtendsFromType;
	var String ClassName;
};
var config array<BTCustomNodeEntry> CustomNodes;

// Cached conditions here so that we don't allocate a zillion of these check conditions.
var transient array<X2Condition> CachedActiveWithAPConditions;
var transient array<X2Condition> CachedActiveDamagedConditions;

struct native specialBehaviorInfo
{
	var bool bIsOverride;
	var bool bAddToTargetDataScore;
	var bool bAddToAlertDataScore;

	var String overrideString;
	var name addToTargetDataScoreName;
	var name addToAlertDataScoreName;

	var name scoreValue;

	structcpptext
	{
		FspecialBehaviorInfo()
		{
			appMemzero(this, sizeof(FspecialBehaviorInfo));
		}
		FspecialBehaviorInfo(EEventParm)
		{
			appMemzero(this, sizeof(FspecialBehaviorInfo));
		}
	}
};

var transient bool bLogUpdateBTQueue;
var private native Map_Mirror specialBehaviorInfomap{ TMap<FName, FspecialBehaviorInfo> };  //  maps table name to index into Behaviors array

native function Name GetNodeName(int Index);
native function int GetNodeIndex(Name BehaviorName);
native static function X2AIBTBehaviorTree GetBehaviorTreeManager();
native function bool HasCycle(int Index, array<int> ParentIndices);
native function OutputRedScreenCycle(array<int> CyclicIndices);

native function InitBehaviors();
native function X2AIBTBehavior GenerateBehaviorTree(Name RootName, Name CharName);
native function X2AIBTBehavior GenerateBehaviorTreeFromIndex(int RootIndex, Name CharName);
native function X2AIBTBehavior CreateBehaviorNode(BehaviorTreeNode kNodeData);
native function X2AIBTBehavior CreateScoringNode( Name strScore, Name strNodeName, Name ParentName );

native function int FindBehaviorIndexInit(Name NodeName);
native function bool IsScoringBehaviorInit(Name BehaviorName, optional out Name strScore, optional out Name strNodeName);

native function int FindBehaviorIndex(Name NodeName);
native function bool IsScoringBehavior(Name BehaviorName, optional out Name strScore, optional out Name strNodeName);

native function bool IsValidBehavior( Name RootName );

native function Name GetNodeTypeOverride(BehaviorTreeNode NodeData, out X2AIBTDecorator Dec, out X2AIBTDefaultConditions Cond, out X2AIBTDefaultActions Act);
native function Class ConstructBTNodeObject(BehaviorTreeNode NodeData, out Name NodeTypeName);

function ClearQueue()
{
	local BTQueueEntry NullEntry;
	ActiveBTQueue.Length = 0;
	bBTQueueTimerActive = false;
	ActiveQueueID = INDEX_NONE;
	ActiveObjectID = INDEX_NONE;
	ActiveBTQueueEntry = NullEntry;
}

function LogNodeDetailText(string strLog)
{
	ActiveNode.LogDetailText(strLog);
}
function String GetLeafParentName()
{
	return ActiveNode.GetLeafParentName();
}

// Prevent more than one behavior tree from running at a time, as this can cause conflicting results.
function bool IsReady()
{
	local X2TacticalGameRuleset Ruleset;

	Ruleset = `TACTICALRULES;
	if (bLogUpdateBTQueue)
	{
		`Log("ActiveObjectID = "$ActiveObjectID@ ", BuildingLatentGameState = "$Ruleset.BuildingLatentGameState);
	}
	return ActiveObjectID == INDEX_NONE && !Ruleset.BuildingLatentGameState;
}

function BeginBehaviorTree( int ObjectID )
{
	if( (ActiveNode != None && ActiveNode.m_eStatus == BTS_RUNNING) || (ActiveObjectID != ObjectID && !IsReady()) )
	{
		`RedScreen("Attempting to start new behavior tree when one is already running! @acheng");
	}
	ActiveObjectID = ObjectID;
}

function EndBehaviorTree(int ObjectID)
{
	if( IsReady() )
	{
		`RedScreen("Attempting to end behavior tree run when none is actively running! @acheng");
	}
	if( ActiveObjectID != ObjectID )
	{
		`RedScreen("Attempting to end behavior tree - object ID mismatch! @acheng");
	}
	ActiveNode = None;
	LastRunningNodeName = '';

	if( ActiveBTQueueEntry.RunCount > 0 )
	{
		ActiveQueueID = ActiveBTQueueEntry.ObjectID;
		ActiveObjectID = ActiveQueueID;
		TryStartBehaviorTreeRun();
	}
	else
	{
		ActiveObjectID = INDEX_NONE;

		//If this is the last unit in the queue, then null out the active scamper unit ID
		ActiveQueueID = ActiveBTQueue.Length == 0 ? INDEX_NONE : ActiveQueueID;
	}
}

private function AddToActiveBTQueue(BTQueueEntry QEntry)
{
	if( ActiveBTQueue.Find('ObjectID', QEntry.ObjectID) == INDEX_NONE )
	{
		ActiveBTQueue.AddItem(QEntry);
	}
}

function TryUpdateBTQueue()
{
	if( !bBTQueueTimerActive )
	{
		UpdateBTQueue();
	}
}

private function UpdateBTQueue()
{
	local XGUnit Unit;
	local XGAIBehavior Behavior;
	bBTQueueTimerActive = true;
	if (bLogUpdateBTQueue)
	{
		`Log("UpdateBTQueue: Entered function.");
	}

	if( !IsReady() )
	{
		if (bLogUpdateBTQueue)
		{
			`Log("UpdateBTQueue: IsReady returned false.");
		}
		`BATTLE.SetTimer(0.1f, false, nameof(UpdateBTQueue), self);
		return;
	}

	if( ActiveQueueID > 0 )
	{
		// Check status of currently active BT.
		Unit = XGUnit(`XCOMHISTORY.GetVisualizer(ActiveQueueID));
		if( Unit != None )
		{
			// Attempt to initialize behavior class if not already set up.
			if (Unit.m_kBehavior == None)
			{
				Unit.InitBehavior();
			}

			Behavior = Unit.m_kBehavior;
			if( Behavior.WaitingForBTRun() )
			{
				if (bLogUpdateBTQueue)
				{
					`Log("UpdateBTQueue: Behavior is WaitingForBTRun.");
				}
				`BATTLE.SetTimer(0.1f, false, nameof(UpdateBTQueue), self);
				return;
			}
		}
		else
		{
			if (bLogUpdateBTQueue)
			{
				`Log("UpdateBTQueue: Unit#"@ActiveQueueID@ " visualizer == None.");
			}
			`BATTLE.SetTimer(0.1f, false, nameof(UpdateBTQueue), self);
			return;  // Waiting for visualizer to be created.
		}

		// Not running currently active BT.  Advance.
		ActiveQueueID = INDEX_NONE;
	}

	if( ActiveBTQueue.Length > 0 )
	{
		if( bWaitingOnSquadConcealment )
		{
			if( !XComPlayerIsConcealed() )
			{
				bWaitingOnSquadConcealment = false;
			}
		}
		if( bWaitingOnEndMoveEvent || bWaitingOnSquadConcealment )
		{
			if (bLogUpdateBTQueue)
			{
				`Log("UpdateBTQueue: bWaitingOnEndMoveEvent="@bWaitingOnEndMoveEvent$ ", bWaitingOnSquadConcealment="@bWaitingOnSquadConcealment);
			}
			`BATTLE.SetTimer(0.1f, false, nameof(UpdateBTQueue), self);
			return;
		}

		// Before updating to the next queue entry, make sure the visualizer exists first.
		Unit = XGUnit(`XCOMHISTORY.GetVisualizer(ActiveBTQueue[0].ObjectID));
		if ( Unit != None )
		{
			ActiveBTQueueEntry = ActiveBTQueue[0];
			if (bLogUpdateBTQueue)
			{
				`Log("UpdateBTQueue: Starting next BT for unit#"@ActiveBTQueueEntry.ObjectID);
			}
			ActiveBTQueue.Remove(0, 1);
			ActiveQueueID = ActiveBTQueueEntry.ObjectID;
			TryStartBehaviorTreeRun();
		}
		`BATTLE.SetTimer(0.1f, false, nameof(UpdateBTQueue), self);
	}
	else
	{
		`LogAI("ActiveBTQueue run complete.");
		bBTQueueTimerActive = false;
	}
}

function bool XComPlayerIsConcealed()
{
	local XComGameStateHistory History;
	local XComGameState_Player XComPlayer;
	History = `XCOMHISTORY;
	foreach History.IterateByClassType(class'XComGameState_Player', XComPlayer)
	{
		if( XComPlayer.GetTeam() == eTeam_XCom )
		{
			return XComPlayer.bSquadIsConcealed;
		}
	}
	return false;
}

// Updating to have BehaviorTreeRuns all kicked off from here.  Handle multiple runs, delays, etc here.
function TryStartBehaviorTreeRun()
{
	local XComGameStateHistory History;
	local int CurrHistoryIndex;
	local XGUnit Unit;
	local XGAIBehavior Behavior;

	History = `XCOMHISTORY;
	// Check history index restriction.  Delay start of BT if we are not yet at this history index.
	CurrHistoryIndex = History.GetCurrentHistoryIndex();
	Unit = XGUnit(History.GetVisualizer(ActiveQueueID));

	if( Unit != None && CurrHistoryIndex >= ActiveBTQueueEntry.HistoryIndex && (ActiveNode == None || ActiveNode.m_eStatus != BTS_RUNNING))
	{
		`LogAI("BTQUEUE: Starting unit #"@ActiveBTQueueEntry.ObjectID@": "$ActiveBTQueueEntry.Node@"RunCount="$ActiveBTQueueEntry.RunCount);
		// Kick off behavior tree, and set the next BT run to start on a delay.
		if( ActiveBTQueueEntry.RunCount > 1 )
		{
			ActiveBTQueueEntry.RunCount -= 1;
		}
		else
		{
			ActiveBTQueueEntry.RunCount = 0;
		}
		`Assert(ActiveQueueID == ActiveBTQueueEntry.ObjectID);
		// Attempt to initialize behavior class if not already set up.
		if (Unit.m_kBehavior == None)
		{
			Unit.InitBehavior();
		}
		Behavior = Unit.m_kBehavior;
		if (Behavior == None)
		{
			`Redscreen("X2AiBTBehaviorTree::TryStartBehaviorTreeRun Unit visualizer exists on unit #"$Unit.ObjectID@ ", but behavior does not! @acheng");
			return;
		}

		// Force patrol group to update alertness values.
		if (Behavior.m_kPatrolGroup != None)
		{
			Behavior.m_kPatrolGroup.UpdateLastAlertLevel();
		}

		Behavior.InitTurn(false);
		if( ActiveBTQueueEntry.bSurprisedScamper )
		{
			Behavior.UseSurprisedScamperMovement();
		}
		Behavior.bBTInitiatedFromEffect = ActiveBTQueueEntry.bInitiatedFromEffect;
		Behavior.StartRunBehaviorTree(ActiveBTQueueEntry.Node,,ActiveBTQueueEntry.bInitFromPlayerEachRun);
	}
	else
	{
		`BATTLE.SetTimer(0.01f, false, nameof(TryStartBehaviorTreeRun), self);
	}
}

function bool IsFirstScamperUnitActive(optional out int ActiveID_out)
{
	if( ActiveQueueID > 0 && ActiveBTQueueEntry.bFirstScamper )
	{
		ActiveID_out = ActiveQueueID;
		return true;
	}
	return false;
}

simulated function bool QueueBehaviorTreeRun(XComGameState_Unit UnitState, string BTRootNode, int RunCount = 1, int StartHistoryIndex = -1, bool bScamperEntry = false, bool bFirstScamper = false, bool bSurprisedScamper = false, bool bInitFromPlayerEachRun=false, bool bInitiatedFromEffect=false)
{
	local BTQueueEntry QEntry;
	`assert(UnitState != none);

	QEntry.Node = name(BTRootNode);
	QEntry.RunCount = RunCount;
	QEntry.ObjectID = UnitState.ObjectID;
	QEntry.HistoryIndex = StartHistoryIndex;
	QEntry.bScamperEntry = bScamperEntry;
	QEntry.bFirstScamper = bFirstScamper;
	QEntry.bSurprisedScamper = bSurprisedScamper;
	QEntry.bInitFromPlayerEachRun = bInitFromPlayerEachRun;
	QEntry.bInitiatedFromEffect = bInitiatedFromEffect;
	AddToActiveBTQueue(QEntry);

	return true;
}

function bool IsQueued(int UnitID)
{
	local int Index;
	Index = ActiveBTQueue.Find('ObjectID', UnitID);
	return Index != INDEX_NONE;
}

function RemoveFromBTQueue(int UnitID, bool bScamperEntryOnly=false)
{
	local int Index;
	Index = ActiveBTQueue.Find('ObjectID', UnitID);
	if( Index != INDEX_NONE )
	{
		if( !bScamperEntryOnly || ActiveBTQueue[Index].bScamperEntry )
		{
			ActiveBTQueue.Remove(Index, 1);
		}
	}
}

function bool IsScampering(int ScamperID=INDEX_NONE, bool bLookInQueue=true)
{
	local int Index;
	local BTQueueEntry QEntry;
	if( ScamperID > 0 )
	{
		if( ActiveQueueID == ScamperID )
		{
			return ActiveBTQueueEntry.bScamperEntry;
		}

		if( bLookInQueue )
		{
			Index = ActiveBTQueue.Find('ObjectID', ScamperID);
			if( Index != INDEX_NONE )
			{
				return ActiveBTQueue[Index].bScamperEntry;
			}
		}
	}
	else
	{
		if( ActiveQueueID > 0 )
		{
			return ActiveBTQueueEntry.bScamperEntry;
		}

		if( bLookInQueue )
		{
			foreach ActiveBTQueue(QEntry)
			{
				if( QEntry.bScamperEntry )
				{
					return true;
				}
			}
		}
	}
	return false;
}

function bool IsGroupScampering(XComGameState_AIGroup GroupState)
{
	local BTQueueEntry QEntry;
	local XComGameStateHistory History;
	local XComGameState_Unit UnitState;
	local XComGameState_AIGroup UnitGroup;
	History = `XCOMHISTORY;
	foreach ActiveBTQueue(QEntry)
	{
		if( QEntry.bScamperEntry )
		{
			UnitState = XComGameState_Unit(History.GetGameStateForObjectID(QEntry.ObjectID));
			UnitGroup = UnitState.GetGroupMembership();
			if( UnitGroup.ObjectID == GroupState.ObjectID )
			{
				return true;
			}
		}
	}
	return false;
}

function ShowActiveBTNode(Canvas kCanvas)
{
	local vector2d ViewportSize;
	local Engine                Engine;
	local int iX, iY;

	Engine = class'Engine'.static.GetEngine();
	Engine.GameViewport.GetViewportSize(ViewportSize);
	iX = ViewportSize.X - 300;
	iY = ViewportSize.Y - 350;

	kCanvas.SetDrawColor(255, 255, 255);
	kCanvas.SetPos(iX, iY);
	if (ActiveObjectID > 0 && LastRunningNodeName != '')
	{
		kCanvas.DrawText("Unit #" @ ActiveObjectID @ ": " $ LastRunningNodeName );
	}
	else
	{
		kCanvas.DrawText("BT inactive.");
	}
}
cpptext
{
	virtual void AddReferencedObjects(TArray<UObject*>& ObjectArray);
	virtual void Serialize(FArchive& Ar);
};

//------------------------------------------------------------------------------------------------
defaultproperties
{
	ActiveObjectID = INDEX_NONE;
}

error: pathspec 'X2WOTCCommunityHighlander/Src/XComGame/Classes/X2AIBTBehaviorTree.uc' did not match any file(s) known to git


#650 - Allows mods to add a modifier to the will recovery project time

//---------------------------------------------------------------------------------------
//  FILE:    XComGameState_HeadquartersProjectRecoverWill.uc
//  AUTHOR:  Mark Nauta  --  08/31/2016
//  PURPOSE: This object represents the instance data for an XCom HQ recover will project
//
//---------------------------------------------------------------------------------------
//  Copyright (c) 2016 Firaxis Games, Inc. All rights reserved.
//---------------------------------------------------------------------------------------
class XComGameState_HeadquartersProjectRecoverWill extends XComGameState_HeadquartersProject;

var int PointsPerBlock;
var int OriginalWorkPerHour;

//---------------------------------------------------------------------------------------
// Call when you start a new project, NewGameState should be none if not coming from tactical
function SetProjectFocus(StateObjectReference FocusRef, optional XComGameState NewGameState, optional StateObjectReference AuxRef)
{
	local XComGameState_Unit UnitState;
	local XComGameStateHistory History;
	local XComGameState_GameTime TimeState;
	local int TotalHours;

	History = `XCOMHISTORY;
	ProjectFocus = FocusRef;
	bIncremental = true;
	bProgressesDuringFlight = true;
	bNoInterruptOnComplete = true;

	if(NewGameState != none)
	{
		UnitState = XComGameState_Unit(NewGameState.GetGameStateForObjectID(ProjectFocus.ObjectID));
	}
	else
	{
		UnitState = XComGameState_Unit(History.GetGameStateForObjectID(ProjectFocus.ObjectID));
	}

	BlocksRemaining = UnitState.GetMaxWillForMentalState(UnitState.GetMentalState()) - UnitState.GetCurrentStat(eStat_Will);

	if(UnitState.GetMentalState() != eMentalState_Ready)
	{
		BlocksRemaining++;
	}

	TotalHours = GetTotalProjectHours(UnitState);
	ProjectPointsRemaining = OriginalWorkPerHour * TotalHours;
	PointsPerBlock = Round(float(ProjectPointsRemaining) / float(BlocksRemaining));

	// Get rid of possible differences caused by rounding
	BlockPointsRemaining = PointsPerBlock;
	ProjectPointsRemaining = PointsPerBlock * BlocksRemaining;
	InitialProjectPoints = ProjectPointsRemaining;

	UpdateWorkPerHour(NewGameState);
	TimeState = XComGameState_GameTime(History.GetSingleGameStateObjectForClass(class'XComGameState_GameTime'));
	StartDateTime = TimeState.CurrentTime;

	if(`STRATEGYRULES != none)
	{
		if(class'X2StrategyGameRulesetDataStructures'.static.LessThan(TimeState.CurrentTime, `STRATEGYRULES.GameTime))
		{
			StartDateTime = `STRATEGYRULES.GameTime;
		}
	}

	if(MakingProgress())
	{
		SetProjectedCompletionDateTime(StartDateTime);
	}
	else
	{
		// Set completion time to unreachable future
		CompletionDateTime.m_iYear = 9999;
		BlockCompletionDateTime.m_iYear = 9999;
	}
}

//---------------------------------------------------------------------------------------
function int CalculateWorkPerHour(optional XComGameState StartState = none, optional bool bAssumeActive = false)
{
	local XComGameStateHistory History;
	local XComGameState_HeadquartersResistance ResHQ;

	History = `XCOMHISTORY;
	ResHQ = XComGameState_HeadquartersResistance(History.GetSingleGameStateObjectForClass(class'XComGameState_HeadquartersResistance'));

	// Issue #650 - added call to TriggerWillRecoveryTimeModifier
	return Round(float(OriginalWorkPerHour) * ResHQ.GetWillRecoveryRateScalar() * TriggerWillRecoveryTimeModifier(StartState, bAssumeActive));
}

// Issue #650 Start
/// HL-Docs: feature:WillRecoveryTimeModifier; issue:650; tags:strategy
/// Allows mods to apply a multiplier to the Will recovery project time, where a
/// value of 1.0 makes no change, 2.0 doubles the duration, etc. Listeners can
/// get the recovering unit from the Will project's `ProjectFocus` property.
///
/// ```event
/// EventID: WillRecoveryTimeModifier,
/// EventData: [
///   inout float TimeMultiplier,
///   in bool bAssumeActive
/// ],
/// EventSource: XComGameState_HeadquartersProjectRecoverWill (ProjectState),
/// NewGameState: maybe
/// ```
///
/// The `NewGameState` and `bAssumeActive` will be whatever caller to CalculateWorkPerHour() passes. Note that
/// both arguments are optional.
///
/// If you want to modify the `TimeMultiplier`, you **must** subscribe with `ELD_Immediate` deferral
private function float TriggerWillRecoveryTimeModifier (optional XComGameState StartState = none, optional bool bAssumeActive = false)
{
	local XComLWTuple Tuple;

	Tuple = new class'XComLWTuple';
	Tuple.Id = 'WillRecoveryTimeModifier';
	Tuple.Data.Add(2);

	Tuple.Data[0].kind = XComLWTVFloat;
	Tuple.Data[0].f = 1.0; // Default no modifier
	Tuple.Data[1].kind = XComLWTVBool;
	Tuple.Data[1].b = bAssumeActive;

	`XEVENTMGR.TriggerEvent('WillRecoveryTimeModifier', Tuple, self, StartState);

	return Tuple.Data[0].f;
}
// Issue #650 End

//---------------------------------------------------------------------------------------
private function int GetTotalProjectHours(XComGameState_Unit UnitState)
{
	local int MinValue, MaxValue, Total, Diff;
	local float PercentToRecover, Days;
	local EMentalState eState;

	// Grab the soldier's mental state
	eState = UnitState.GetMentalState(true);

	// Calculate the percent of our current mental state that needs recovering
	MinValue = UnitState.GetMinWillForMentalState(eState);
	MaxValue = UnitState.GetMaxWillForMentalState(eState);

	Total = MaxValue - MinValue;
	Diff = UnitState.GetCurrentStat(eStat_Will) - MinValue;
	PercentToRecover = 1.0f - (float(Diff) / float(Total));

	// Apply this percent to scale between min and max days recovering
	MinValue = class'X2StrategyGameRulesetDataStructures'.default.WillRecoveryDays[eState].MinDays;
	MaxValue = class'X2StrategyGameRulesetDataStructures'.default.WillRecoveryDays[eState].MaxDays;
	Total = MaxValue - MinValue;
	Days = float(MinValue);
	Days += (PercentToRecover * float(Total));

	return Round(Days * 24.0f);
}

//---------------------------------------------------------------------------------------
// Heal the unit by one block, and check if the healing is complete
function OnBlockCompleted()
{
	local XComGameState NewGameState;
	local XComGameState_Unit UnitState;
	local XComGameStateHistory History;
	local XComGameState_HeadquartersProjectRecoverWill WillProject;

	History = `XCOMHISTORY;
	NewGameState = class'XComGameStateContext_ChangeContainer'.static.CreateChangeState("Unit Recovered Will - 1 Block");
	UnitState = XComGameState_Unit(History.GetGameStateForObjectID(ProjectFocus.ObjectID));

	if(UnitState != none)
	{
		UnitState = XComGameState_Unit(NewGameState.ModifyStateObject(class'XComGameState_Unit', UnitState.ObjectID));
		UnitState.SetCurrentStat(eStat_Will, UnitState.GetCurrentStat(eStat_Will) + 1);

		WillProject = XComGameState_HeadquartersProjectRecoverWill(NewGameState.ModifyStateObject(class' XComGameState_HeadquartersProjectRecoverWill', self.ObjectID));
		WillProject.BlocksRemaining--;

		if(WillProject.BlocksRemaining > 0)
		{
			WillProject.BlockPointsRemaining = WillProject.PointsPerBlock;
			WillProject.ProjectPointsRemaining = WillProject.BlocksRemaining * WillProject.BlockPointsRemaining;
			WillProject.UpdateWorkPerHour();
			WillProject.StartDateTime = `STRATEGYRULES.GameTime;

			if(WillProject.MakingProgress())
			{
				WillProject.SetProjectedCompletionDateTime(WillProject.StartDateTime);
			}
			else
			{
				// Set completion time to unreachable future
				WillProject.CompletionDateTime.m_iYear = 9999;
				WillProject.BlockCompletionDateTime.m_iYear = 9999;
			}
		}

		`XCOMGAME.GameRuleset.SubmitGameState(NewGameState);
	}
	else
	{
		History.CleanupPendingGameState(NewGameState);
	}
}

//---------------------------------------------------------------------------------------
// Remove the project and the engineer from the room's repair slot
function OnProjectCompleted()
{
	local XComGameStateHistory History;
	local XComGameState NewGameState;
	local XComGameState_HeadquartersXCom XComHQ;
	local XComGameState_Unit UnitState;
	local array<StrategyCostScalar> CostScalars;
	local EMentalState StartingState;

	History = `XCOMHISTORY;
	NewGameState = class'XComGameStateContext_ChangeContainer'.static.CreateChangeState("On Recover Will Project Completed");

	// Remove the project
	XComHQ = XComGameState_HeadquartersXCom(History.GetSingleGameStateObjectForClass(class'XComGameState_HeadquartersXCom'));
	XComHQ = XComGameState_HeadquartersXCom(NewGameState.ModifyStateObject(class'XComGameState_HeadquartersXCom', XComHQ.ObjectID));
	XComHQ.Projects.RemoveItem(self.GetReference());

	// Set soldier will to max
	UnitState = XComGameState_Unit(NewGameState.ModifyStateObject(class'XComGameState_Unit', ProjectFocus.ObjectID));
	StartingState = UnitState.GetMentalState(true);
	UnitState.SetCurrentStat(eStat_Will, UnitState.GetMaxStat(eStat_Will));
	UnitState.UpdateMentalState();

	// Check if soldier is boosted and no longer needs the boost
	if(UnitState.bRecoveryBoosted && !UnitState.IsInjured())
	{
		UnitState.UnBoostSoldier(true);

		// HQ refund cost of boosting
		CostScalars.Length = 0;
		XComHQ.RefundStrategyCost(NewGameState, class'X2StrategyElement_XpackFacilities'.default.BoostSoldierCost, CostScalars);
	}

	NewGameState.RemoveStateObject(self.ObjectID);

	`XCOMGAME.GameRuleset.SubmitGameState(NewGameState);

	if(StartingState == eMentalState_Shaken)
	{
		`HQPRES.Notify(Repl(ProjectCompleteNotification, "%UNIT", UnitState.GetName(eNameType_RankFull)), class'UIUtilities_Image'.const.EventQueue_Staff);
	}
}

//---------------------------------------------------------------------------------------
DefaultProperties
{
	OriginalWorkPerHour=10
}

//---------------------------------------------------------------------------------------
//  FILE:    XComGameState_HeadquartersProjectRecoverWill.uc
//  AUTHOR:  Mark Nauta  --  08/31/2016
//  PURPOSE: This object represents the instance data for an XCom HQ recover will project
//
//---------------------------------------------------------------------------------------
//  Copyright (c) 2016 Firaxis Games, Inc. All rights reserved.
//---------------------------------------------------------------------------------------
class XComGameState_HeadquartersProjectRecoverWill extends XComGameState_HeadquartersProject;

var int PointsPerBlock;
var int OriginalWorkPerHour;

//---------------------------------------------------------------------------------------
// Call when you start a new project, NewGameState should be none if not coming from tactical
function SetProjectFocus(StateObjectReference FocusRef, optional XComGameState NewGameState, optional StateObjectReference AuxRef)
{
	local XComGameState_Unit UnitState;
	local XComGameStateHistory History;
	local XComGameState_GameTime TimeState;
	local int TotalHours;

	History = `XCOMHISTORY;
	ProjectFocus = FocusRef;
	bIncremental = true;
	bProgressesDuringFlight = true;
	bNoInterruptOnComplete = true;

	if(NewGameState != none)
	{
		UnitState = XComGameState_Unit(NewGameState.GetGameStateForObjectID(ProjectFocus.ObjectID));
	}
	else
	{
		UnitState = XComGameState_Unit(History.GetGameStateForObjectID(ProjectFocus.ObjectID));
	}

	BlocksRemaining = UnitState.GetMaxWillForMentalState(UnitState.GetMentalState()) - UnitState.GetCurrentStat(eStat_Will);

	if(UnitState.GetMentalState() != eMentalState_Ready)
	{
		BlocksRemaining++;
	}

	TotalHours = GetTotalProjectHours(UnitState);
	ProjectPointsRemaining = OriginalWorkPerHour * TotalHours;
	PointsPerBlock = Round(float(ProjectPointsRemaining) / float(BlocksRemaining));

	// Get rid of possible differences caused by rounding
	BlockPointsRemaining = PointsPerBlock;
	ProjectPointsRemaining = PointsPerBlock * BlocksRemaining;
	InitialProjectPoints = ProjectPointsRemaining;

	UpdateWorkPerHour(NewGameState);
	TimeState = XComGameState_GameTime(History.GetSingleGameStateObjectForClass(class'XComGameState_GameTime'));
	StartDateTime = TimeState.CurrentTime;

	if(`STRATEGYRULES != none)
	{
		if(class'X2StrategyGameRulesetDataStructures'.static.LessThan(TimeState.CurrentTime, `STRATEGYRULES.GameTime))
		{
			StartDateTime = `STRATEGYRULES.GameTime;
		}
	}

	if(MakingProgress())
	{
		SetProjectedCompletionDateTime(StartDateTime);
	}
	else
	{
		// Set completion time to unreachable future
		CompletionDateTime.m_iYear = 9999;
		BlockCompletionDateTime.m_iYear = 9999;
	}
}

//---------------------------------------------------------------------------------------
function int CalculateWorkPerHour(optional XComGameState StartState = none, optional bool bAssumeActive = false)
{
	local XComGameStateHistory History;
	local XComGameState_HeadquartersResistance ResHQ;

	History = `XCOMHISTORY;
	ResHQ = XComGameState_HeadquartersResistance(History.GetSingleGameStateObjectForClass(class'XComGameState_HeadquartersResistance'));

	return Round(float(OriginalWorkPerHour) * ResHQ.GetWillRecoveryRateScalar());
}

//---------------------------------------------------------------------------------------
private function int GetTotalProjectHours(XComGameState_Unit UnitState)
{
	local int MinValue, MaxValue, Total, Diff;
	local float PercentToRecover, Days;
	local EMentalState eState;

	// Grab the soldier's mental state
	eState = UnitState.GetMentalState(true);

	// Calculate the percent of our current mental state that needs recovering
	MinValue = UnitState.GetMinWillForMentalState(eState);
	MaxValue = UnitState.GetMaxWillForMentalState(eState);

	Total = MaxValue - MinValue;
	Diff = UnitState.GetCurrentStat(eStat_Will) - MinValue;
	PercentToRecover = 1.0f - (float(Diff) / float(Total));

	// Apply this percent to scale between min and max days recovering
	MinValue = class'X2StrategyGameRulesetDataStructures'.default.WillRecoveryDays[eState].MinDays;
	MaxValue = class'X2StrategyGameRulesetDataStructures'.default.WillRecoveryDays[eState].MaxDays;
	Total = MaxValue - MinValue;
	Days = float(MinValue);
	Days += (PercentToRecover * float(Total));

	return Round(Days * 24.0f);
}

//---------------------------------------------------------------------------------------
// Heal the unit by one block, and check if the healing is complete
function OnBlockCompleted()
{
	local XComGameState NewGameState;
	local XComGameState_Unit UnitState;
	local XComGameStateHistory History;
	local XComGameState_HeadquartersProjectRecoverWill WillProject;

	History = `XCOMHISTORY;
	NewGameState = class'XComGameStateContext_ChangeContainer'.static.CreateChangeState("Unit Recovered Will - 1 Block");
	UnitState = XComGameState_Unit(History.GetGameStateForObjectID(ProjectFocus.ObjectID));

	if(UnitState != none)
	{
		UnitState = XComGameState_Unit(NewGameState.ModifyStateObject(class'XComGameState_Unit', UnitState.ObjectID));
		UnitState.SetCurrentStat(eStat_Will, UnitState.GetCurrentStat(eStat_Will) + 1);

		WillProject = XComGameState_HeadquartersProjectRecoverWill(NewGameState.ModifyStateObject(class' XComGameState_HeadquartersProjectRecoverWill', self.ObjectID));
		WillProject.BlocksRemaining--;

		if(WillProject.BlocksRemaining > 0)
		{
			WillProject.BlockPointsRemaining = WillProject.PointsPerBlock;
			WillProject.ProjectPointsRemaining = WillProject.BlocksRemaining * WillProject.BlockPointsRemaining;
			WillProject.UpdateWorkPerHour();
			WillProject.StartDateTime = `STRATEGYRULES.GameTime;

			if(WillProject.MakingProgress())
			{
				WillProject.SetProjectedCompletionDateTime(WillProject.StartDateTime);
			}
			else
			{
				// Set completion time to unreachable future
				WillProject.CompletionDateTime.m_iYear = 9999;
				WillProject.BlockCompletionDateTime.m_iYear = 9999;
			}
		}

		`XCOMGAME.GameRuleset.SubmitGameState(NewGameState);
	}
	else
	{
		History.CleanupPendingGameState(NewGameState);
	}
}

//---------------------------------------------------------------------------------------
// Remove the project and the engineer from the room's repair slot
function OnProjectCompleted()
{
	local XComGameStateHistory History;
	local XComGameState NewGameState;
	local XComGameState_HeadquartersXCom XComHQ;
	local XComGameState_Unit UnitState;
	local array<StrategyCostScalar> CostScalars;
	local EMentalState StartingState;

	History = `XCOMHISTORY;
	NewGameState = class'XComGameStateContext_ChangeContainer'.static.CreateChangeState("On Recover Will Project Completed");

	// Remove the project
	XComHQ = XComGameState_HeadquartersXCom(History.GetSingleGameStateObjectForClass(class'XComGameState_HeadquartersXCom'));
	XComHQ = XComGameState_HeadquartersXCom(NewGameState.ModifyStateObject(class'XComGameState_HeadquartersXCom', XComHQ.ObjectID));
	XComHQ.Projects.RemoveItem(self.GetReference());

	// Set soldier will to max
	UnitState = XComGameState_Unit(NewGameState.ModifyStateObject(class'XComGameState_Unit', ProjectFocus.ObjectID));
	StartingState = UnitState.GetMentalState(true);
	UnitState.SetCurrentStat(eStat_Will, UnitState.GetMaxStat(eStat_Will));
	UnitState.UpdateMentalState();

	// Check if soldier is boosted and no longer needs the boost
	if(UnitState.bRecoveryBoosted && !UnitState.IsInjured())
	{
		UnitState.UnBoostSoldier(true);

		// HQ refund cost of boosting
		CostScalars.Length = 0;
		XComHQ.RefundStrategyCost(NewGameState, class'X2StrategyElement_XpackFacilities'.default.BoostSoldierCost, CostScalars);
	}

	NewGameState.RemoveStateObject(self.ObjectID);

	`XCOMGAME.GameRuleset.SubmitGameState(NewGameState);

	if(StartingState == eMentalState_Shaken)
	{
		`HQPRES.Notify(Repl(ProjectCompleteNotification, "%UNIT", UnitState.GetName(eNameType_RankFull)), class'UIUtilities_Image'.const.EventQueue_Staff);
	}
}

//---------------------------------------------------------------------------------------
DefaultProperties
{
	OriginalWorkPerHour=10
}