Author Topic: Tech Tree alterations, and C++ coding for adding/hiding Research Tech  (Read 2171 times)

Offline dave_erald

  • Sr. Member
  • ****
  • Posts: 263
I have already asked SirBomber about this, he's fairly certain there is no way to do this without altering OP2 itself (which the more I look at it the more I am willing to agree I won't be able to implement this), so maybe there is another way around...


I am looking at creating my own tech tree.

I would like to have two separate Tech lines (Weapons Tech that go two different routes) so that if one is researched the other disappears.
Player has option to research down Weapons Tech 'A' or Weapons Tech 'B'. If 'A' is researched 'B' disappears and so on and so forth.

Or is it possible to have OP2 to read from two different tech files, or the ability to load a different one mid game?

How do they program hiding techs in the tutorials? Which is what I believe is what they do until you fulfill in game requirements and then extra tech is added... I think. Now I need to go find that level and look over what it's doing, I'm probably wrong on how its implemented.


Anyways, maybe someone else has stumbled across this, or a way of doing something similar which would be nice. I do see the CreateResearchTrigger and Player[0].MarkResearchComplete functions but I know this isn't the way.

Any help Thanks! And thanks SirBomber for the tutorials and answering up to this point.

« Last Edit: December 27, 2015, 02:23:18 PM by dave_erald »
-David R.V.

-GMT400 fan
-OPU Influencer

Offline Hooman

  • Administrator
  • Hero Member
  • *****
  • Posts: 4744
Re: Tech Tree alterations, and C++ coding for adding/hiding Research Tech
« Reply #1 on: December 27, 2015, 10:01:55 PM »
You've put some interesting thought into this.

Sirbomber is right in that there is no way to do this in the tech file alone.

The game has tech levels, the thousands place of the tech ID. The campaign missions let you research everything up to the tech level of the campaign mission number. Everything beyond that is hidden. This is not useful behaviour for what you want to do.

Mission objectives can be added during gameplay, such as having to research a new tech that has just become available, because the prerequisites have been researched. This is also not what you want because it's simply an announcement of requirements to finish the mission, and doesn't actually affect what can be researched.

An additional complication for what you want to do, is if the player builds two labs to research the start of each tree independently. What happens when both research topics have completed? Are both trees unlocked?

Ignoring that problem, one idea I have, which requires mission support, is to make the two trees depend on two unavailable hidden techs that can not be researched. This effectively removes both research trees from the game by default. Have two visible techs to unlock these two trees, and when research one of them completes, call MarkResearchComplete on the corresponding hidden tech to unlock that tree, but only if the other visible tech hasn't been researched yet. Essentially you can put the conditional logic in a trigger callback within the mission DLL, outside of the actual tech tree.

Failing that, there are also memory hacks you could use to mess with the tech tree, but they'd probably also rely on research complete triggers, so why not try it the above way first.

Offline dave_erald

  • Sr. Member
  • ****
  • Posts: 263
Re: Tech Tree alterations, and C++ coding for adding/hiding Research Tech
« Reply #2 on: December 28, 2015, 12:49:22 AM »
That all makes sense in my head, and you're right, something like this would have to be coded into every mission that uses this specific research tech.txt file.

Coding it however, not my forte...

So it would look something like this=>

CreateResearchTrigger (1, 1, 2701, 0, "HiddenAResearched")
CreateResearchTrigger (1, 1, 2702, 0, "HiddenBResearched")

Visible 'A' researched (triggers Hidden 'A') -> MarkResearchComplete Hidden 'A'(trigger controlled, disables Hidden trigger 'B' if done first) -> Unlocks tech direction 'A'

Visible 'B' researched (triggers Hidden 'B') -> MarkResearchComplete Hidden 'B'(trigger controlled, disables Hidden trigger 'A' if done first) -> Unlocks tech direction 'B'

I can see bits and pieces of this but have no idea how to string it together into usable code.
-David R.V.

-GMT400 fan
-OPU Influencer

Offline Hooman

  • Administrator
  • Hero Member
  • *****
  • Posts: 4744
Re: Tech Tree alterations, and C++ coding for adding/hiding Research Tech
« Reply #3 on: December 28, 2015, 01:12:05 AM »
The string at the end of CreateResearchTrigger is the name of your callback function (which must be exported). The name is arbitrarily defined by you, ...but should probably be "VisibleAResearched"and "VisibleBResearched".  ;)

Within the callback you'd want to store the choice somewhere permanent. See examples of using ScriptGlobal, and the whole issue with GetSaveRegions. If you store the choice outside of such a struct, it won't be remembered if you save and reload your game.

You'll probably want to mark the other visible research completed as well, so it disappears from the lab. I forgot to mention that before.

You'll also need to mark the hidden tech as researched, unlocking the tree that depends on it.

Code: [Select]
Export VisibleAResearched()
{
  // Exit if a choice has already been made
  if (scriptGlobal.researchChoice != 0) return;

  // Mark the choice as having been made, never to be changed again
  scriptGlobal.researchChoice = 1; // Or somehow mark the choice of 'A' has been made

  // Make visible tech B disappear as a research option, since it should no longer be possible
  localPlayer.MarkResearchComplete(visibleTechB);

  // Unlock the chosen tech tree
  localPlayer.MarkResearchComplete(hiddenTechA);
}

Export VisibleBResearched()
{
  // ...
}

Offline dave_erald

  • Sr. Member
  • ****
  • Posts: 263
Re: Tech Tree alterations, and C++ coding for adding/hiding Research Tech
« Reply #4 on: December 28, 2015, 01:19:31 AM »
Alright, now you're just showing off.

I was curious how to disable the other side, just making the unused visible tech as MarkResearchComplete I should have figured out myself.



So Export VisibleBResearched would essentially be the same just a for b and vice-versa?

This could work. Thanks Guy.
-David R.V.

-GMT400 fan
-OPU Influencer

Offline Hooman

  • Administrator
  • Hero Member
  • *****
  • Posts: 4744
Re: Tech Tree alterations, and C++ coding for adding/hiding Research Tech
« Reply #5 on: December 28, 2015, 02:21:45 AM »
Pretty much the same for the other callback, and also change:
Code: [Select]
scriptGlobal.researchChoice = 2; // Or somehow mark the choice of 'B' has been made

You could potentially use an enum or named constants instead of 0, 1, 2 to denote the choice (or lack thereof).


You can also clean up the design a little. The code between the two cases is largely the same, so you can extract the code into a common function. The callback functions can then just call the common function and pass it a parameter to distinguish the choice.
Code: [Select]
Export VisibleAResearched()
{
  SetResearchChoice(1);
}

Export VisibleBResearched()
{
  SetResearchChoice(2);
}


// No "Export" needed for this function. Parameter could be an enum instead of an int, or whatever distinguishes cases
void SetResearchChoice(int choice)
{
  // Generic code here...

  // Exit if a choice has already been made
  if (scriptGlobal.researchChoice != 0) return;

  // Mark the choice as having been made, never to be changed again
  scriptGlobal.researchChoice = choice;

  // Make visible techs disappear as a research option, since a choice has now been made
  localPlayer.MarkResearchComplete(visibleTechA);
  localPlayer.MarkResearchComplete(visibleTechB);

  // Unlock the chosen tech tree
  // Details omitted about how "choice" maps to "hiddenTeachChoice"
  // A switch could be used, or if tech ID numbers are consecutive, the choice could be added to a base value
  localPlayer.MarkResearchComplete(hiddenTechChoice);
}

Offline dave_erald

  • Sr. Member
  • ****
  • Posts: 263
Re: Tech Tree alterations, and C++ coding for adding/hiding Research Tech
« Reply #6 on: December 28, 2015, 04:51:25 PM »
So Visual Studio doesn't support Export anymore,

switch to extern and it still wants int or void or something in front of VisibleAResearched

and trying to find examples of ScriptGlobal, i may have that right...
-David R.V.

-GMT400 fan
-OPU Influencer

Offline dave_erald

  • Sr. Member
  • ****
  • Posts: 263
Re: Tech Tree alterations, and C++ coding for adding/hiding Research Tech
« Reply #7 on: December 28, 2015, 06:27:57 PM »
This is what I got, which is wrong

Code: [Select]
struct ScriptGlobal
{
int researchChoice;
};

ScriptGlobal scriptGlobal;


and this


Code: [Select]
extern void VisibleAResearched()
{
// Exit if a choice has already been made
if (scriptGlobal.researchChoice != 0) return;

// Mark the choice as having been made, never to be changed again
scriptGlobal.researchChoice = 1; // Or somehow mark the choice of 'A' has been made

// Make visible tech B disappear as a research option, since it should no longer be possible
Player[0].MarkResearchComplete(6101);

// Unlock the chosen tech tree
Player[0].MarkResearchComplete(6103);
}

extern void VisibleBResearched()
{
// Exit if a choice has already been made
if (scriptGlobal.researchChoice != 0) return;

// Mark the choice as having been made, never to be changed again
scriptGlobal.researchChoice = 2; // Or somehow mark the choice of 'A' has been made

// Make visible tech B disappear as a research option, since it should no longer be possible
Player[0].MarkResearchComplete(6102);

// Unlock the chosen tech tree
Player[0].MarkResearchComplete(6104);
}

It builds, there are no syntax errors, but the game says Can Not resolve data reference in game.
I'm missing some things I think. This is as far as I got till I can learn some more.
-David R.V.

-GMT400 fan
-OPU Influencer

Offline Hooman

  • Administrator
  • Hero Member
  • *****
  • Posts: 4744
Re: Tech Tree alterations, and C++ coding for adding/hiding Research Tech
« Reply #8 on: December 28, 2015, 11:15:06 PM »
Extern is defined in the Outpost2DLL SDK, in file RequiredExports.h:
Code: [Select]
#define Export extern "C" __declspec(dllexport)

You're right, I missed the void return type.
Code: [Select]
Export void VisibleAResearched()

I also noticed in the SDK that many of the exported functions are also declared __cdecl. I'm not sure if that's absolutely required, especially if there are no arguments, but here's an example.
Code: [Select]
//Export void __cdecl NoResponseToTrigger();	// Used for triggers with no custom effects

The above is a function declaration, but not a definition, hence it ends with a semicolon ";", rather than a code block "{}" containing the function body. You'll of course need to define the body of the function.


When you call the createTrigger functions, you pass the name of the trigger callback function. Outpost 2 will attempt to lookup this name as an exported function from the level DLL. The name must match exactly, or Outpost 2 will give an error about not being able to resolve the reference.

To export a function from the DLL, as opposed to just making it visible to other compilation units (.cpp files), you need to use the __declspec(dllexport) part. This creates an entry in the DLL's export table with the name and address of your function. The name does not necessarily match what's in your code exactly though, due to things like name mangling. To disable the name mangling, you need the extern "C" part. There is a difference between how source code names map to exported names between C and C++ code. In C++ you can have overloaded functions, which means more than one function with the same name, but different parameters. The compiler is able to tell which function you want to call based on the types of the parameters you supply. Linkers are generally simple though, and aren't aware of parameter lists, so they need to see two different names to link up functions correctly. Hence C++ names use "name mangling", or "decorated names", which encodes information on the parameters into the names, giving overloaded functions distinct names for the linker. Information on calling conventions can also affect the decorated names. Naturally this extra information needs to be stripped to get the exported name that you expect.


The Export macro was likely added or changed later than other code, so depending on where you got the SDK from, it might be missing.

Offline dave_erald

  • Sr. Member
  • ****
  • Posts: 263
Re: Tech Tree alterations, and C++ coding for adding/hiding Research Tech
« Reply #9 on: December 28, 2015, 11:45:49 PM »
Sweet lord of stank it works.

Cool, now what?


Anyways, here's all of the code and it's locations, so far all in the main.cpp file
Again, this is just me, dickin around, in a test map, I don't code, it's ugly but I sort of get how it works...

Code: [Select]
int InitProc()
{
Player[0].GoEden();          // Forces a player to play as Eden
Player[0].SetColorNumber(4); // Forces a player to play using the specified color
{
CreateResearchTrigger(1, 1, 6101, 0, "VisibleAResearched");

CreateResearchTrigger(1, 1, 6102, 0, "VisibleBResearched");
}

This is just out on it's own in main.cpp

Code: [Select]
struct ScriptGlobal
{
int researchChoice;
};

ScriptGlobal scriptGlobal;

And this is just sitting out on it's own...

Code: [Select]
Export void VisibleAResearched()
{
// Exit if a choice has already been made
if (scriptGlobal.researchChoice != 0) return;

// Mark the choice as having been made, never to be changed again
scriptGlobal.researchChoice = 1; // Or somehow mark the choice of 'A' has been made

// Make visible tech B disappear as a research option, since it should no longer be possible
Player[0].MarkResearchComplete(6102);

// Unlock the chosen tech tree
Player[0].MarkResearchComplete(6103);
};

Export void VisibleBResearched()
{
// Exit if a choice has already been made
if (scriptGlobal.researchChoice != 0) return;

// Mark the choice as having been made, never to be changed again
scriptGlobal.researchChoice = 2; // Or somehow mark the choice of 'A' has been made

// Make visible tech A disappear as a research option, since it should no longer be possible
Player[0].MarkResearchComplete(6101);

// Unlock the chosen tech tree
Player[0].MarkResearchComplete(6104);
};

So I have tested both ways, researching tech 'A' brings up the next tech in line for 'A' and removes everything for tech 'B' and vice-versa.

     Cool. Thanks for the help man!
Ultimately I hope to now use this towards a completely new tech tree (Two actually, one=> Last one Standing and one=> for Colony Game's/Land Rush/Space Race or survivor). Whether the small number of people left really playing this game will be up for it remains to be seen. Naturally the only way these trees will work is for extra coding into each map, I wonder how this coding would need to be altered for multiplayer use, and how I would save it in the GetSaveRegions? I'm sure MrBomber's tutorials ought to clear that up.

And when we (by we I should probably declare it could very well be mostly you) get this lint parser up and going this is the perfect place to run it.

Thanks again!


EDIT: I guess I should put in the tech tree bits that make this work if anyone was curious. Ignore the descriptions and tech cost numbers, it's just for testing purposes right now.

BEGIN_TECH "Weapons Tech Direction A" 06101
    CATEGORY        7
    DESCRIPTION     "Weapons Tech A.  _______________________________________ Research into Weapons Tech Line A."
    TEASER          "Weapons Research 'A'.  _______________________________________ Our scientists are of two minds, half of them want to pursue Long Range Weaponry (Tech A), the other half Ballistics Impact Focus (Tech B) geared towards oversizing our munitions. As Commander we are giving you final say, please choose one."
    REQUIRES        02701
    REQUIRES        02702
    EDEN_COST       50
    PLYMOUTH_COST   50
    MAX_SCIENTISTS  5
    LAB             2
END_TECH

BEGIN_TECH "Weapons Tech Direction B" 06102
    CATEGORY        7
    DESCRIPTION     "Weapons Tech B.  _______________________________________ Research into Weapons Tech Line B."
    TEASER          "Weapons Research 'B'.  _______________________________________ Our scientists are of two minds, half of them want to pursue Long Range Weaponry (Tech A), the other half Ballistics Impact Focus (Tech B) geared towards oversizing our munitions. As Commander we are giving you final say, please choose one."
    REQUIRES        02701
    REQUIRES        02702
    EDEN_COST       50
    PLYMOUTH_COST   50
    MAX_SCIENTISTS  5
    LAB             2
END_TECH

BEGIN_TECH "Weapons Tech checker A" 06105
    CATEGORY        7
    DESCRIPTION     "Congrats it worked."
    TEASER          "Congrats it worked."
    REQUIRES        06103
    EDEN_COST       50
    PLYMOUTH_COST   50
    MAX_SCIENTISTS  5
    LAB             2
END_TECH

BEGIN_TECH "Weapons Tech checker B" 06106
    CATEGORY        7
    DESCRIPTION     "Congrats it worked."
    TEASER          "Congrats it worked."
    REQUIRES        06104
    EDEN_COST       50
    PLYMOUTH_COST   50
    MAX_SCIENTISTS  5
    LAB             2
END_TECH

;******************************************************************************
; Group Hidden Techs => Weapons Research
;******************************************************************************


BEGIN_TECH "Weapons Tech A" 06103
    CATEGORY        0
    COST            -1
END_TECH

BEGIN_TECH "Weapons Tech B" 06104
    CATEGORY        0
    COST            -1
END_TECH
« Last Edit: December 28, 2015, 11:53:56 PM by dave_erald »
-David R.V.

-GMT400 fan
-OPU Influencer

Offline Hooman

  • Administrator
  • Hero Member
  • *****
  • Posts: 4744
Re: Tech Tree alterations, and C++ coding for adding/hiding Research Tech
« Reply #10 on: December 29, 2015, 01:01:22 AM »
Nice job.  :)


Looks like you have reasonable code. Here are a few suggestions.

You can remove the {} around your CreateResearchTrigger lines. It's not technically wrong, but it's weird. The braces create a new code block, with a new scope for variables. There is no point to do this here. Blocks are mostly used to group code to control execution flow, such as around function bodies, if/else statements, for/do/while loops, and exception handler try/catch blocks. Unless you have a very good reason to control local variable scope and lifetime, you should probably avoid using {} outside of those contexts. In your case, there is no flow control there, and there are no local variables defined within the block, so it does nothing.

As for the comment "Forces a player to play as Eden", I find "force" sounds weird. There usually is no choice for things like colony games. The level chooses for the player. It's essentially a "set" method, to use a more common term. The level can always change the decision later by calling the method again. That would be weird to call it again though. Less weird would be to have the level use a random number and set the colony type using an if statement. In multiplayer, the method would override what the player chooses, so in that sense I suppose you're correct.

The ScriptGlobal code looks right. There still needs to be a GetSaveRegions export that tells Outpost 2 about that struct. In RequiredExports.h there is a macro to define that function for you. The macro definition looks like this:
Code: [Select]
// Generate function to return global variable struct to Outpost2.exe for game save/load
#define ExportSaveLoadData(globalVarStructName) \
Export void __cdecl GetSaveRegions(BufferDesc& bufferDesc) \
{ \
bufferDesc.bufferStart = &globalVarStructName; \
bufferDesc.length = sizeof(globalVarStructName); \
}

You can use this macro after defining your struct and declaring a variable of that struct type. You can even define the struct and declare a variable of that type together. Note the variable name at the end of the struct declaration, before the semicolon. This let's you save a line of code. After that, the macro expands to the function definition that Outpost 2 calls to find your struct.
Code: [Select]
struct ScriptGlobal
{
int researchChoice;
} scriptGlobal;
ExportSaveLoadData(scriptGlobal);

If you had no global data to save/load from saved game files, you could use the alternative macro:
Code: [Select]
// Generate function to return a lack of global variables for game save/load
#define ExportSaveLoadDataNone() \
Export void __cdecl GetSaveRegions(BufferDesc& bufferDesc) \
{ \
bufferDesc.bufferStart = 0; \
bufferDesc.length = 0; \
}

You might want to update some of the comments for the trigger callback functions. ;)

For your example tech file, you can unlock the chosen research tree using:
Code: [Select]
	// Unlock the chosen tech tree
Player[0].MarkResearchComplete(6102 + scriptGlobal.researchChoice);

That eliminates the key differences between your two methods. The other minor difference is removing the choice techs, but you can just remove both by marking them as complete, including the one that just finished. Having no differences allows you to extract the guts of the trigger callbacks into one common function with a parameter for researchChoice, like I alluded to in an earlier post.
« Last Edit: December 29, 2015, 01:04:51 AM by Hooman »