Getting going after set-up

Get help with installing and running the Cafu Engine here. This forum is also for general questions and discussion of all aspects regarding the Cafu Engine.
Post Reply
SoulRider
Posts:95
Joined:2014-04-06, 00:16
Getting going after set-up

Post by SoulRider » 2014-04-15, 04:52

I thought I'd start a new thread to ask a few questions that don't seem to be covered in the FAQ. Forgive me if they are.

Do you know of a simple way to create a solution file for the project?

Is there a way for me to clone your git repository across services? (I use GitHub).

How do I compile the games and run them without needing the source code?
While I am prototyping the game, I'd like to be able to share it with friends so they can run it and give me feedback.

Also is there an easier way to run the tools?
It would be nice just to be able to compile the tools as stand-alones to make development easier.

The GUI scripts appear to be in lua, but they are in a custom file format. Is there a reason they do not have the .lua extension?

Are entity hierarchies still available?
While I understand the concept of the component system, it too has it's drawback without class hierarchies. For example, to implement a version of TF2, with it's 9 players classes, I would need to create 9 individual, often complicated, component systems, that are largely the same. Some degree of hierarchy is needed to utilise fully scalability. I may have misunderstood, but as far as I can tell, you have switch to a component only system?

There is currently no team component, while I could in theory (eventually) create something in lua, would this be better implemented in the entity system?
It may not be worth it, although I don't know if lua can effect the entities used in the editor yet (team spawns), I haven't got that far :D

I am really loving the work you have done, and once I get going properly, I will be excited to see what I can create.

I like to give myself little challenges to spur my development, so I have decided to make a new mod to add to the current tech demo. Again, it will be nice and simple, as this is my first attempt at building a game from the ground up, but it will be an interesting experience. It may also take some time..
User avatar
Carsten
Site Admin
Posts:2170
Joined:2004-08-19, 13:46
Location:Germany
Contact:

Re: Getting going after set-up

Post by Carsten » 2014-04-15, 17:43

Hi SoulRider,
SoulRider wrote:I thought I'd start a new thread
New thread for new (set of) questions is always a good idea, :thx:!
Do you know of a simple way to create a solution file for the project?
Unfortunately, no. See Where are the Visual Studio project files? and IDEs and Text Editors for some info about this.
Is there a way for me to clone your git repository across services? (I use GitHub).
I'm by no means a Git expert, but this should be easy. Just add your (initially empty) GitHub repository as another remote to your local clone of the Cafu repository. You should then be able to push the branches into the GitHub remote repository.
How do I compile the games and run them without needing the source code?
The by far easiest way is probably to copy the entire Cafu (top-level) directory onto an USB stick.

Nothing else is needed, Cafu is "portable" by design, but the "debug" builds can consume a lot of disk space.

To fight this, you could create release builds only, e.g. clone again into a separate directory, but before the first run of SCons, modify CompilerSetup.py(.tmpl) regarding the buildVariants setting.

Yet another option is to use the make_binary.py script from https://bitbucket.org/cafu/tools, but this too is still tailored to the master branch, and may or may not work with entity-component-system as-is.
Also is there an easier way to run the tools?
It would be nice just to be able to compile the tools as stand-alones to make development easier.
If I understood your question right, please check out my recent reply to Andrey, where I have answered the same question, too. ;-)
The GUI scripts appear to be in lua, but they are in a custom file format. Is there a reason they do not have the .lua extension?
Just historical: They did not use to be Lua, and have become "true" Lua scripts only later.

(If syntax highlighting is a problem, most editors should be able to be taught which type of highlighting to apply to which file extension...)
Are entity hierarchies still available?
While I understand the concept of the component system, it too has it's drawback without class hierarchies. For example, to implement a version of TF2, with it's 9 players classes, I would need to create 9 individual, often complicated, component systems, that are largely the same. Some degree of hierarchy is needed to utilise fully scalability. I may have misunderstood, but as far as I can tell, you have switch to a component only system?
The component system will no longer feature "entity (C++) class hierarchies", but that should not be a limiting factor: Components, of which entities are now composed, are normal, plain C++ classes. They themselves can form class hierarchies, and/or they can hold member variables who in turn can belong to class hierarchies.

More generally, and for completeness, I must add that the human player is certainly the most complex of all entities. Even in the simple DeathMatch game, where a player can pick up weapons and carry several of them, the resulting code is more complex than it initially seems -- and (to the best of my understanding) not very predisposed or even halfway suitable to be easily broken into components (which are normally intended to be largely independent from each other).

For example, whatever weapon the player currently holds, it affects both its 3rd person weapon model (that other players see), its 1st person weapon model (that we ourselves see), it can be reloaded from or restock (when picked up) one of more ammo slots in our inventory (but other weapons may use the same ammo slots as well), and implementing features like "switch to the next 'suitable' or 'similar' weapon" can require a global overview of all of the players inventory and available weapons, there are holster and draw animations to account for, etc. etc.

So the details can be tricky. Therefore, with the DeathMatch MOD, I currently plan to put most of this (the weapon handling) into a single, monolithic component. (But I'm open to and happy to hear suggestions about this!) Btw., both the old and the new DeathMatch code uses a class hierarchy for "carried weapons" (often abbreviated "cw" in the code).

Based on this, in your version of TF2, it looks to me as if this was one of the very few areas where C++ programming would be the right (or in fact, mandatory) tool for making a player component that can handle all the weapons and player classes. You would of course be able to use any C++ technology (especially class hierarchies) that helps achieving this.
There is currently no team component, while I could in theory (eventually) create something in lua, would this be better implemented in the entity system?
In C++, yes.
It may not be worth it, although I don't know if lua can effect the entities used in the editor yet (team spawns), I haven't got that far :D
Dynamically, not yet. Later, it is planned and should be possible to launch the game (even if without pre-rendered lightmaps, and possibly at reduced performance) directly in the Map Editor.
I like to give myself little challenges to spur my development, so I have decided to make a new mod to add to the current tech demo. Again, it will be nice and simple, as this is my first attempt at building a game from the ground up, but it will be an interesting experience. It may also take some time..
It's great to hear that! As I mentioned in my recent reply to Andrey linked above, we're in the midst of the largest changes that Cafu has ever undergone. As long as you can tolerate the occasional frustration resulting from that, it will certainly work out! :up:
Best regards,
Carsten
SoulRider
Posts:95
Joined:2014-04-06, 00:16

Re: Getting going after set-up

Post by SoulRider » 2014-04-15, 18:39

Thanks for the replies. Here are a couple of clarifications :D
If I understood your question right, please check out my recent reply to Andrey, where I have answered the same question, too. ;-)
I don't see my question explained there. Normally, when you write an application, you can build it into an executable for release to the general public. It would be easier for development of games if the world editor could be compiled into a standalone tool. This could then be passed out to mappers and texture artists to work on games, without needing all the source code and complicated instructions that go with it. As an example, I just mentioned class hierarchies to my texture guy, and he said 'it sounds like Latin to me'. He certainly wouldn't be able to build the game and run it himself. While I understand the portability is a great benefit, sometimes an exe is easier :D
The component system will no longer feature "entity (C++) class hierarchies", but that should not be a limiting factor: Components, of which entities are now composed, are normal, plain C++ classes. They themselves can form class hierarchies, and/or they can hold member variables who in turn can belong to class hierarchies.

Based on this, in your version of TF2, it looks to me as if this was one of the very few areas where C++ programming would be the right (or in fact, mandatory) tool for making a player component that can handle all the weapons and player classes. You would of course be able to use any C++ technology (especially class hierarchies) that helps achieving this.
I still am going over this system and re-reading as it is new to me, so it is taking a little while to sink in. The TF2 was a simple example, I should have used NS2, you have Entity>ScriptActor>Player>Alien>AlienClass. While All components behind player can be used as components, all parts after really need a hierarchical system.

For me, the ideal solution would be to create the entity as is done currently, then from that entity component data, build a base class in lua. So human player would be as it is, the entity components loaded into the lua script, then the components are created within a class that can be sub classed in lua.
Is this possible currently?
As I mentioned in my recent reply to Andrey linked above, we're in the midst of the largest changes that Cafu has ever undergone. As long as you can tolerate the occasional frustration resulting from that, it will certainly work out! :up:
I started playing NS2, when all they had released was a box with a rifle and some pop-up targets. I have been modding NS2, since before all the playable characters existed in the game, through weekly updates changing the whole content structure, sometimes completely rewriting how a function worked, or indeed even whole classes!

As a complete non-experienced programmer, spending 2.5 years doing that taught me a few things about dealing with ever changing code bases, but the beauty here is I get to go to the next stage. I was modifying ns2 as it was built from this level up, now I am building a game from this level up, that is exciting. I'd rather leave the C++ editing for my 2nd game, as I think mastering lua is going to be the most important part of this challenge :D
User avatar
Carsten
Site Admin
Posts:2170
Joined:2004-08-19, 13:46
Location:Germany
Contact:

Re: Getting going after set-up

Post by Carsten » 2014-04-16, 17:53

Hi SoulRider,
SoulRider wrote:Normally, when you write an application, you can build it into an executable for release to the general public. It would be easier for development of games if the world editor could be compiled into a standalone tool. This could then be passed out to mappers and texture artists to work on games, without needing all the source code and complicated instructions that go with it.
Ok, I see, but stand-alone tools is exactly what we have. The make_binary.py script that I mentioned above is used for preparing our binary, ready-to-use, stand-alone releases. What is essentially does is this:
  • compile from source code as you did by hand,
  • copy the exe files from the build/.../release directories to the top-level directory (so that you can double-click them, and the path is immediately right ;-) ),
  • delete the build and source-code directories.
This is why I said in my previous post "The by far easiest way is probably to copy the entire Cafu (top-level) directory onto an USB stick.", which is nearly the same, but lacks the second and third steps in my above list, which improve the user experience, but don't make a technical change.

What can be called a "downside" in all cases is that our programs must load resources (textures, icons, sounds, maps), which are located in files on disk, in directories like Fonts or Games below the Cafu "root" or top-level directory. All programs must be able to find these directories, without which they are indeed not stand-alone.

So... I'm still not sure... would it help if I revised the make_binary.py script, making sure is good for use with the entity-component-system branch, so that you can use it to built a binary, self-contained release for others?
I still am going over this system and re-reading as it is new to me, so it is taking a little while to sink in. The TF2 was a simple example, I should have used NS2, you have Entity>ScriptActor>Player>Alien>AlienClass. While All components behind player can be used as components, all parts after really need a hierarchical system.
First a general note :cheesy: : Please believe me that I was one of the strongest defenders and proponents of classic C++ class hierarchies, but no longer am. I did not swing into the full opposite though, and certainly can see that class hierarchies in game entity programming can still be useful, but now that I've fully understood the idea and seen the pro's (and also a few smaller con's), it seems to me as if almost anything can be done with components, and in a better way. But in any case, I'm not fixated on anything here, and I'm perfectly sure that we can get things just right.

More specifically, back to the Alien example hierarchy that you outlined above: Does this hierarchy have additional classes, i.e. what other classes derive from Player, Alien, AlienClass? It seems useful to me if we discussed an example that is slightly larger than this, i.e. an example where classes have more child-classes than one?
For me, the ideal solution would be to create the entity as is done currently, then from that entity component data, build a base class in lua. So human player would be as it is, the entity components loaded into the lua script, then the components are created within a class that can be sub classed in lua.
Is this possible currently?
I'm not sure what you try to achieve...

For example, with the component system, a human player entity will, among others, have a Model component for its body model, a Sound component for footstep sounds, and more components e.g. for physics, collision, etc. You would be able to access these components from Lua script, and deal with them as desired.

The human player would also and unconditionally have a Script component, which is the most important piece for custom Lua scripting. In it, you can do anything: Control the behavior of the player, access or modify all other components, and if desired, store any extra data that you need and that is not yet kept in any of the components.

Well, this all feels quite abstract to me, and I'd certainly be happy if we had something more concrete to discuss it. ;-)
I started playing NS2, when all they had released was a box with a rifle and some pop-up targets. I have been modding NS2, since before all the playable characters existed in the game, through weekly updates changing the whole content structure, sometimes completely rewriting how a function worked, or indeed even whole classes!

As a complete non-experienced programmer, spending 2.5 years doing that taught me a few things about dealing with ever changing code bases, but the beauty here is I get to go to the next stage. I was modifying ns2 as it was built from this level up, now I am building a game from this level up, that is exciting. I'd rather leave the C++ editing for my 2nd game, as I think mastering lua is going to be the most important part of this challenge :D
Wow, thanks for that info, it sounds very good! :wohow:
Best regards,
Carsten
SoulRider
Posts:95
Joined:2014-04-06, 00:16

Re: Getting going after set-up

Post by SoulRider » 2014-04-16, 18:44

Carsten wrote: So... I'm still not sure... would it help if I revised the make_binary.py script, making sure is good for use with the entity-component-system branch, so that you can use it to built a binary, self-contained release for others?
That would be brilliant. It would make it much easier for me to get things developed and tested. It's good to know these features already exist, it's just getting to know about them :D

For me, the ideal solution would be individual distributable .exe files for CaWE, Ca3DE. I would be able to then bundle these into an installer, or just share the files in a zip, for people to use. I would just create the following folder structure:

GameName\
Fonts
Games
Misc
CaWE.exe
Cafu.exe

If there are any other files or folders needed, just let me know and I'll add them to the list.

The only thing that is going to be a little difficult is getting the maps compiled. Is there anyway to make map compiling a function of CaWE?

Carsten wrote: More specifically, back to the Alien example hierarchy that you outlined above: Does this hierarchy have additional classes, i.e. what other classes derive from Player, Alien, AlienClass? It seems useful to me if we discussed an example that is slightly larger than this, i.e. an example where classes have more child-classes than one?
Here is the full class system for Players on NS2. Root > Child - Child

Player > Marine - Alien - Spectator - Commander - ReadyRoom Player - Exo

Marine > JetPackMarine
Alien > Skulk - Lerk - Fade - Onos
Spectator > TeamSpectator - FilmSpectator - GUISpectator
Commander > AlienCommander - MarineComander

TeamSpec > AlienSpectator - MarineSpectator

That is the class structure NS2 used from Player down.

In NS2 the full path is Entity>ScriptActor>Player>etc.

Note that only the Entity class is a C++ engine class, all the further code is created entirely openly in lua. This is what is causing me a little confusion, as previously I've not had to touch C++, and now in Cafu HumanPlayer (Marine equivalent), is in C++. It is quite a separation for me, and it will take me a little time to get used to. C++ is much less forgiving than lua :?
User avatar
Carsten
Site Admin
Posts:2170
Joined:2004-08-19, 13:46
Location:Germany
Contact:

Re: Getting going after set-up

Post by Carsten » 2014-04-19, 23:44

Hi SoulRider,
SoulRider wrote:
Carsten wrote: So... I'm still not sure... would it help if I revised the make_binary.py script, making sure is good for use with the entity-component-system branch, so that you can use it to built a binary, self-contained release for others?
That would be brilliant. It would make it much easier for me to get things developed and tested. It's good to know these features already exist, it's just getting to know about them :D
Ok. I started looking into this, and although the existing make_binary.py script works, I still want to make a couple of changes to it (that will also help with future steps towards "continuous integration"), and break it into smaller, individual parts that are more easily reused on their own (e.g. for turning an existing repository checkout that is already built and equipped with textures and world etc. into a binary-only release).
For me, the ideal solution would be individual distributable .exe files for CaWE, Ca3DE. I would be able to then bundle these into an installer, or just share the files in a zip, for people to use. I would just create the following folder structure: [...]
Yes, that's essentially what it amounts to. However, as I mentioned earlier, there are still several "dependencies" (images, maps, ...) that we cannot do without, so besides the .exe files, these must all be there as well.

If you don't mind the extra bandwidth, pick up one of the binary releases from the Downloads page. Each of these essentially is a "minimal binary" / "minimal stand-alone" edition, without source code, but with texture images, sounds, precompiled maps, and other auxiliary files that are needed. These may give you an idea what to expect.
The only thing that is going to be a little difficult is getting the maps compiled. Is there anyway to make map compiling a function of CaWE?
Yes, please check out where additional info is provided (I've not yet proof-read these texts and functionality with the entity-component-system branch, though.)

The earlier mentioned command-line call

Code: Select all

python Games\DeathMatch\compileMaps.py BPRockB
is quasi in the mid between these two approaches, i.e. the hand-made "raw" calls to the individual map compilers, and the convenient method in CaWE.

Carsten wrote: [...] It seems useful to me if we discussed an example that is slightly larger than this, i.e. an example where classes have more child-classes than one?
Here is the full class system for Players on NS2. [...]
Ok, many thanks for the details!

Originally, when I asked for a larger example, I was hoping to be able to use that example in a step-by-step, tutorial-like manner to demonstrate how you would be able to implement, what the classic class hierarchy achieved, in terms of the Component System. I'm still 100% positive that that is possible, but it looks to me that I don't know enough about the NS2 code to conduct this "tutorial" in a convincing manner at the abstract level that would be necessary at this point.

Just let me try to summarize, very briefly and certainly incomplete / not exhaustive: With the Component System, if the entity is represented by a model, it has a Model component. If it makes a sound, it has a Sound component, and so on. This is true for all types (classes) of entity: monsters, human players, ...
What really distinguishes monster-type "A" from monster-type "B" from human players is their "behavior". And for this, we have Script components, which bring you immediately into Lua, so that from there on, you're free to implement the entity behavior as your heart desires (and also e.g. to configure the Model and Sound components...).
Note that only the Entity class is a C++ engine class, all the further code is created entirely openly in lua. This is what is causing me a little confusion, as previously I've not had to touch C++, and now in Cafu HumanPlayer (Marine equivalent), is in C++. It is quite a separation for me, and it will take me a little time to get used to. C++ is much less forgiving than lua :?
I don't think that that will be a problem. Right now, I work as quickly as possible to implement the Human Player code in Lua -- that is the gist of the entity Component System, after all. Ideally, if you were to re-make NS2 with Cafu, you would and should be able to implement all of the above in Lua, too.

Also, don't get distracted by the fact that you've seen the Human Player code implemented in C++ already. That is (very) old code. It may happen that one of my next steps is nothing more than moving that very code into a component -- where it is C++ as well. But that would only happen to buy me some extra time and freedom to deal with other details first, such as a consideration of the ramifications of the client prediction feature. Eventually, the Human Player behavior code will be implemented in Lua, hopefully sooner rather than later. In fact, the plan is to be able to make new games with Cafu without being required to write new C++ code for it (but you can if you want, as my first Human Player implementation in the Component System may likely prove ;-) ). The Script components are the game developer's C++-to-Lua "interface" in that regard.

Btw., I plan to hide the 3rd person player model that can currently wrongly be seen, and I also plan to fast-forward branch master to entity-component-system soon, then delete branch entity-component-system. This is because plenty of fixes have accumulated in entity-component-system that really everyone should get, and it seems better to me if you all don't stick back on previous revisions... I'll let you know explicitly when that's ready.
Best regards,
Carsten
SoulRider
Posts:95
Joined:2014-04-06, 00:16

Re: Getting going after set-up

Post by SoulRider » 2014-04-20, 17:32

Thanks for all your replies.

I am just reading things and going backwards and forwards in the code. I now have a much better understanding of the component system, I now understand it to work very much like the mixin system used in NS2, but to a much higher level. I am really excited by this.

The only issue I have currently is not understanding the syntax and rules of C++. My current challenge is to implement a Team component into the engine. It is supposed to be a very basic component which just holds a team number.

I used the CompBasics as a base, and while I understand the majority of it, getting it to work is proving harder than I first thought. I will get there eventually, I am just currently reading through the internet trying to get explanations about the parts of the code I don't understand :D

I am really excited about this, and I am also learning that a large majority of my issues are coming from my lack of understanding of the finer details of programming, rather than the functionality of the code in the engine, so forgive me if some of my questions seem a bit confusing at times :D
SoulRider
Posts:95
Joined:2014-04-06, 00:16

Re: Getting going after set-up

Post by SoulRider » 2014-04-20, 18:26

To further expand on my new understanding of the component system, I thought I'd post a simple example that highlights both the similarities and differences between the NS2 implementation and the Cafu implementation. This will expand on what I currently understand, and also issues I face between the two engines. In this example I will use the concept of Teams.

In NS2 there is a lua script class called Team.lua. It won't make total sense as it calls global variables etc, but you should be able to get the gist of what it does from the variable names anyway:

Code: Select all

// ======= Copyright (c) 2003-2012, Unknown Worlds Entertainment, Inc. All rights reserved. =====
//
// lua\Team.lua
//
//    Created by:   Charlie Cleveland (charlie@unknownworlds.com) and
//                  Max McGuire (max@unknownworlds.com)
//
// Tracks players on a team.
//
// ========= For more information, visit us at http://www.unknownworlds.com =====================

class 'Team'

function Team:Initialize(teamName, teamNumber)

    self.teamName = teamName
    self.teamNumber = teamNumber
    self.playerIds = table.array(16)
    self.respawnQueue = table.array(16)
    // This is a special queue to place players in if the
    // teams become unbalanced.
    self.respawnQueueTeamBalance = table.array(16)
    self.kills = 0
    
end

function Team:Uninitialize()
end

function Team:OnCreate()
end

function Team:OnInitialized()
end

function Team:OnEntityKilled(targetEntity, killer, doer, point, direction)

    local killerOnTeam = HasMixin(killer, "Team") and killer:GetTeamNumber() == self.teamNumber
    if killer and targetEntity and killerOnTeam and GetAreEnemies(killer, targetEntity) and killer:isa("Player") and targetEntity:isa("Player") then
        self:AddKills(1)
    end
    
end

/**
 * If a team doesn't support orders then any player changing to the team will have it's
 * orders cleared.
 */
function Team:GetSupportsOrders()
    return true
end

/**
 * Called only by Gamerules.
 */
function Team:AddPlayer(player)

    if player ~= nil and player:isa("Player") then
    
        // Update scores when switching teams.
        player:SetRequestsScores(true)
        local id = player:GetId()
        return table.insertunique(self.playerIds, id)
        
    else
        Print("Team:AddPlayer(): Entity must be player (was %s)", SafeClassName(player))
    end
    
    return false
    
end

local function UpdateRespawnQueueTeamBalance(self)

    // Check if a player needs to be removed from the holding area.
    while #self.respawnQueueTeamBalance > (self.autoTeamBalanceAmount or 0) do
    
        local spawnPlayer = Shared.GetEntity(self.respawnQueueTeamBalance[1])
        table.remove(self.respawnQueueTeamBalance, 1)
        
        spawnPlayer:SetRespawnQueueEntryTime(Shared.GetTime())
        table.insertunique(self.respawnQueue, spawnPlayer:GetId())
        
        spawnPlayer:SetWaitingForTeamBalance(false)
        
        TEST_EVENT("Auto-team balance, out of queue")
        
    end
    
end

function Team:OnEntityChange(oldId, newId)

    // Replace any entities in the respawn queue
    if oldId and table.removevalue(self.respawnQueue, oldId) then
    
        // Keep queue entry time the same
        if newId then
            table.insertunique(self.respawnQueue, newId)
        end
        
    end
    
    if oldId and table.removevalue(self.respawnQueueTeamBalance, oldId) then
    
        if newId then
        
            table.insertunique(self.respawnQueue, newId)
            Shared.GetEntity(newId):SetWaitingForTeamBalance(true)
            
        end
        
    end
    
    UpdateRespawnQueueTeamBalance(self)
    
end

function Team:GetPlayer(playerIndex)

    if (playerIndex >= 1 and playerIndex <= table.count(self.playerIds)) then
        return Shared.GetEntity( self.playerIds[playerIndex] )
    end
    
    Print("Team:GetPlayer(%d): Invalid index specified (1 to %d)", playerIndex, table.count(self.playerIds))
    return nil
    
end

/**
 * Called only by Gamerules.
 */
function Team:RemovePlayer(player)

    assert(player)
    
    if not table.removevalue(self.playerIds, player:GetId()) then
        Print("Player %s with Id %d not in playerId list.", player:GetClassName(), player:GetId())
    end
    
    self:RemovePlayerFromRespawnQueue(player)
    
    player:SetTeamNumber(kTeamInvalid)
    
end

function Team:GetNumPlayers()

    local numPlayers = 0
    local numRookies = 0
    
    // Player may have been deleted this tick, so check id to make sure player count is correct)
    for index, playerId in ipairs(self.playerIds) do
    
        local player = Shared.GetEntity(playerId)
        // Verify the player has a Client attached to it (we don't want to count ragdolls as team players for example.
        if player ~= nil and player:GetId() ~= Entity.invalidId and Server.GetOwner(player) ~= nil then
            numPlayers = numPlayers + 1
            if player:GetIsRookie() then
                numRookies = numRookies + 1
            end
        end
        
    end
    
    return numPlayers, numRookies
    
end

function Team:GetNumPlayersInQueue()
    return #self.respawnQueue
end

function Team:GetNumDeadPlayers()

    local numPlayers = 0
    
    // Player may have been deleted this tick, so check id to make sure player count is correct)
    for index, playerId in ipairs(self.playerIds) do

        local player = Shared.GetEntity(playerId)
        if player ~= nil and player:GetId() ~= Entity.invalidId and player:GetIsAlive() == false then
        
            numPlayers = numPlayers + 1
            
        end
        
    end
    
    return numPlayers
    
end

function Team:GetPlayers()

    local playerList = {}
    for index, playerId in ipairs(self.playerIds) do

        local player = Shared.GetEntity(playerId)
        if player ~= nil and player:GetId() ~= Entity.invalidId then
        
            table.insert(playerList, player)
            
        end
        
    end
    
    return playerList
    
end

function Team:GetTeamNumber()
    return self.teamNumber
end

// Called on game start or end. Reset everything but teamNumber and teamName.
function Team:Reset()

    self.kills = 0
    
    self:ClearRespawnQueue()
    
    // Clear players
    self.playerIds = { }
    
end

function Team:ResetPreservePlayers(techPoint)

    local playersOnTeam = {}
    table.copy(self.playerIds, playersOnTeam)
    
    if Shared.GetCheatsEnabled() and techPoint ~= nil then
        Print("Setting team %d team location: %s", self:GetTeamNumber(), techPoint:GetLocationName())
    end
    
    if techPoint then
        self.initialTechPointId = techPoint:GetId()
    end
    
    self:Reset()
    
    table.copy(playersOnTeam, self.playerIds)    
    
end

/** 
 * Play sound for every player on the team.
 */
function Team:PlayPrivateTeamSound(soundName, origin, commandersOnly, excludePlayer, ignoreDistance, triggeringPlayer)

    ignoreDistance = ignoreDistance or false
    
    local function PlayPrivateSound(player)
    
        if ( not commandersOnly or player:isa("Commander") ) and (not triggeringPlayer or not triggeringPlayer:isa("Player") or GetGamerules():GetCanPlayerHearPlayer(player, triggeringPlayer)) then
            if excludePlayer ~= player then
                // Play alerts for commander at commander origin, so they always hear them
                if not origin or player:isa("Commander") then
                    Server.PlayPrivateSound(player, soundName, player, 1.0, Vector(0, 0, 0), ignoreDistance)
                else
                    Server.PlayPrivateSound(player, soundName, nil, 1.0, origin, ignoreDistance)
                end
            end
        end
        
    end
    
    self:ForEachPlayer(PlayPrivateSound)
    
end

function Team:TriggerEffects(eventName)

    local function TriggerEffects(player)
        player:TriggerEffects(eventName)
    end
    
    self:ForEachPlayer(TriggerEffects)
end

function Team:SetFrozenState(state)

    local function SetFrozen(player)
        player.frozen = state
    end
    
    self:ForEachPlayer(SetFrozen)
    
end

function Team:SetAutoTeamBalanceEnabled(enabled, unbalanceAmount)

    self.autoTeamBalanceEnabled = enabled
    self.autoTeamBalanceAmount = enabled and unbalanceAmount or nil
    
    UpdateRespawnQueueTeamBalance(self)
    
end

/**
 * Queues a player to be spawned.
 */
function Team:PutPlayerInRespawnQueue(player)

    assert(player)
    
    // Place player in a "holding area" if auto-team balance is enabled.
    if self.autoTeamBalanceEnabled then
    
        // Place this new player into the holding area.
        table.insert(self.respawnQueueTeamBalance, player:GetId())
        
        player:SetWaitingForTeamBalance(true)
        
        UpdateRespawnQueueTeamBalance(self)
        
        TEST_EVENT("Auto-team balance, in queue")
        
    else
    
        local extraTime = 0
        if player.spawnBlockTime then
            extraTime = math.max(0, player.spawnBlockTime - Shared.GetTime())
        end
        
        if player.spawnReductionTime then
            extraTime = extraTime - player.spawnReductionTime
            player.spawnReductionTime = nil
        end
    
        player:SetRespawnQueueEntryTime(Shared.GetTime() + extraTime)
        table.insertunique(self.respawnQueue, player:GetId())
        
        if self.OnRespawnQueueChanged then
            self:OnRespawnQueueChanged()
        end
        
    end
    
end

function Team:GetPlayerPositionInRespawnQueue(player)

    local queueSize = #self.respawnQueue
    for i = 1, queueSize do
        
        if player:GetId() == self.respawnQueue[i] then
            return i
        end
        
    end
    
    return -1

end

/**
 * Removes the player from the team's spawn queue (if he's in it, otherwise has
 * no effect).
 */
function Team:RemovePlayerFromRespawnQueue(player)

    table.removevalue(self.respawnQueueTeamBalance, player:GetId())
    table.removevalue(self.respawnQueue, player:GetId())
    
    UpdateRespawnQueueTeamBalance(self)
    
    player:SetWaitingForTeamBalance(false)
    
end

function Team:ClearRespawnQueue()

    for p = 1, #self.respawnQueueTeamBalance do
    
        local player = Shared.GetEntity(self.respawnQueueTeamBalance[p])
        player:SetWaitingForTeamBalance(false)
        
    end
    
    table.clear(self.respawnQueueTeamBalance)
    table.clear(self.respawnQueue)
    
end

// Find player that's been dead and waiting the longest. Return nil if there are none.
function Team:GetOldestQueuedPlayer()

    local playerToSpawn = nil
    local earliestTime = -1
    
    for i, playerId in ipairs(self.respawnQueue) do
    
        local player = Shared.GetEntity(playerId)
        
        if player ~= nil and player.GetRespawnQueueEntryTime then
        
            local currentPlayerTime = player:GetRespawnQueueEntryTime()
            
            if currentPlayerTime ~= nil and (earliestTime == -1 or currentPlayerTime < earliestTime) then
            
                playerToSpawn = player
                earliestTime = currentPlayerTime
                
            end
            
        end
        
    end
    
    if playerToSpawn and ( not playerToSpawn.spawnBlockTime or playerToSpawn.spawnBlockTime <= Shared.GetTime() ) then    
        return playerToSpawn
    end
    
end

function Team:GetSortedRespawnQueue()

    local sortedQueue = {}
    
    for i = 1, #self.respawnQueue do
    
        local player = Shared.GetEntity(self.respawnQueue[i])
        if player then
            table.insertunique(sortedQueue, player)
        end
    
    end
    
    local function SortByEntryTime(player1, player2) 

        local time1 = player1.GetRespawnQueueEntryTime and player1:GetRespawnQueueEntryTime() or 0
        local time2 = player2.GetRespawnQueueEntryTime and player2:GetRespawnQueueEntryTime() or 0
        
        return time1 < time2
        
    end
    
    table.sort(sortedQueue, SortByEntryTime)
    
    return sortedQueue

end

function Team:GetKills()
    return self.kills
end

function Team:AddKills(num)
    self.kills = self.kills + num
end

// Structure was created. May or may not be built or active.
function Team:StructureCreated(entity)
end

// Entity that supports the tech tree was just added (it's built/active).
function Team:TechAdded(entity) 
end

// Entity that supports the tech tree was just removed (no longer built/active).
function Team:TechRemoved(entity)    
end

function Team:GetIsPlayerOnTeam(player)
    return table.find(self.playerIds, player:GetId()) ~= nil    
end

function Team:ReplaceRespawnAllPlayers()

    local playerIds = table.duplicate(self.playerIds)

    for i, playerIndex in ipairs(playerIds) do
    
        local player = Shared.GetEntity(playerIndex)
        self:ReplaceRespawnPlayer(player, nil, nil)

    end
    
end

// For every player on team, call functor(player)
function Team:ForEachPlayer(functor)

    for i, playerIndex in ipairs(self.playerIds) do
    
        local player = Shared.GetEntity(playerIndex)
        if player ~= nil and player:isa("Player") then
            functor(player)
        else
            Print("Team:ForEachPlayer(): Couldn't find player for index %d", playerIndex)
        end
        
    end
    
end

function Team:SendCommand(command)

    local function PlayerSendCommand(player)
        Server.SendCommand(player, command)
    end
    self:ForEachPlayer(PlayerSendCommand)
    
end

function Team:GetHasActivePlayers()

    local hasActivePlayers = false
    local currentTeam = self

    local function HasActivePlayers(player)
        if(player:GetIsAlive() and (player:GetTeam() == currentTeam)) then
            hasActivePlayers = true
        end
    end

    self:ForEachPlayer(HasActivePlayers)
    return hasActivePlayers

end

function Team:GetHasAbilityToRespawn()
    return true
end

function Team:Update(timePassed)
end

function Team:GetNumCommandStructures()

    local commandStructures = GetEntitiesForTeam("CommandStructure", self:GetTeamNumber())
    return table.maxn(commandStructures)
    
end

function Team:GetNumAliveCommandStructures()

    local commandStructures = GetEntitiesForTeam("CommandStructure", self:GetTeamNumber())
    
    local numAlive = 0
    for c = 1, #commandStructures do
        numAlive = commandStructures[c]:GetIsAlive() and (numAlive + 1) or numAlive
    end
    return numAlive
    
end

function Team:GetHasTeamLost()
    return false    
end

function Team:GetHasTeamWon()
    return false    
end

function Team:RespawnPlayer(player, origin, angles)

    assert(self:GetIsPlayerOnTeam(player), "Player isn't on team!")
    
    if origin == nil or angles == nil then
    
        // Randomly choose unobstructed spawn points to respawn the player
        local spawnPoint = nil
        local spawnPoints = Server.readyRoomSpawnList
        local numSpawnPoints = table.maxn(spawnPoints)
        
        if numSpawnPoints > 0 then
        
            local spawnPoint = GetRandomClearSpawnPoint(player, spawnPoints)
            if spawnPoint ~= nil then
            
                origin = spawnPoint:GetOrigin()
                angles = spawnPoint:GetAngles()
                
            end
            
        end
        
    end
    
    // Move origin up and drop it to floor to prevent stuck issues with floating errors or slightly misplaced spawns
    if origin ~= nil then
    
        SpawnPlayerAtPoint(player, origin, angles)
        
        player:ClearEffects()
        
        return true
        
    else
        Print("Team:RespawnPlayer(player, %s, %s) - Must specify origin.", ToString(origin), ToString(angles))
    end
    
    return false
    
end

function Team:BroadcastMessage(message)

    function sendMessage(player)
        Server.Broadcast(player, message)
    end
    
    self:ForEachPlayer( sendMessage )
    
end

function Team:GetSpectatorMapName()
    return Spectator.kMapName
end
I understand this class, and it's sub-classes could all be implemented in scripts in Cafu as well. The class system is as follows:

Team > Playing Team - Spectating Team - Ready Room Team
Playing Team > Marine Team - Alien Team

So that is the team code created, but entities still need to be assigned to a team. In NS2, this is controlled by what they call a 'Mixin'. I know this is not the correct term neccesarily, but it works. This TeamMixin is called by entities that are on a team and sets the teamNumber Network Variable that is defined in the Mixin. As I understand it, this use of a Mixin is equivalent to a component in Cafu.

However, this is where the simlilarities end and the difference begins. NS2 mixins are not written in the engine in C++, they are all written in lua. The engine has an API that hooks into certain elements, but all of what you seem to be defining as components in C++ are defined as mixins in lua. As as example, here is the TeamMixin.lua that I am essentially trying to implement as a CompTeam in Cafu:

Code: Select all

// ======= Copyright (c) 2003-2011, Unknown Worlds Entertainment, Inc. All rights reserved. =======    
//    
// lua\TeamMixin.lua    
//    
//    Created by:   Brian Cronin (brianc@unknownworlds.com)    
//    
// ========= For more information, visit us at http://www.unknownworlds.com =====================    

/**
 * TeamMixin has functionality for an Entity to be on a team.
 */
TeamMixin = CreateMixin(TeamMixin)
TeamMixin.type = "Team"

TeamMixin.networkVars =
{
    // Never set this directly, call SetTeamNumber()
    teamNumber = string.format("integer (-1 to %d)", kSpectatorIndex)
}

function TeamMixin:__initmixin()
    self.teamNumber = -1
end

if Client then

    function TeamMixin:OnGetIsVisible(visibleTable, viewerTeamNumber)
    
        local player = Client.GetLocalPlayer()
        
        if player and player:isa("Commander") and viewerTeamNumber == GetEnemyTeamNumber(self:GetTeamNumber()) and HasMixin(self, "LOS") and not self:GetIsSighted() then
            visibleTable.Visible = false
        end
        
    end
    
end

local kTeamIndexToType = { }
kTeamIndexToType[kTeamInvalid] = kNeutralTeamType
kTeamIndexToType[kTeamReadyRoom] = kNeutralTeamType
kTeamIndexToType[kTeam1Index] = kTeam1Type
kTeamIndexToType[kTeam2Index] = kTeam2Type
kTeamIndexToType[kSpectatorIndex] = kNeutralTeamType

function TeamMixin:GetTeamType()
    return kTeamIndexToType[self.teamNumber]
end

function TeamMixin:GetTeamNumber()
    return self.teamNumber
end

function TeamMixin:SetTeamNumber(teamNumber)

    self.teamNumber = teamNumber
    
    if self.OnTeamChange then
        self:OnTeamChange()
    end
    
end

function TeamMixin:OnInitialized()

    local teamNumber = GetAndCheckValue(self.teamNumber, 0, 3, "teamNumber", 0, true)
    self:SetTeamNumber(teamNumber)
    
end

/**
 * The team object only exists on the Server.
 */
function TeamMixin:GetTeam()

    assert(Server)
    
    if not GetHasGameRules() then
        return nil
    end
    
    return GetGamerules():GetTeam(self:GetTeamNumber())
    
end

function TeamMixin:GetEffectParams(tableParams)

    if not tableParams[kEffectFilterIsMarine] then
        tableParams[kEffectFilterIsMarine] = (self:GetTeamType() == kMarineTeamType)
    end
    
    if not tableParams[kEffectFilterIsAlien] then
        tableParams[kEffectFilterIsAlien] = (self:GetTeamType() == kAlienTeamType)
    end
    
end

/**
 * This function is called from OwnerMixin when ownership of this
 * object changes.
 */
function TeamMixin:OnOwnerChanged(oldOwner, newOwner)

    if newOwner and HasMixin(newOwner, "Team") then
    
        // Only allow team members to own this object.
        if newOwner:GetTeamNumber() ~= self:GetTeamNumber() then
            self:SetOwner(nil)
        end
        
    end
    
end

function TeamMixin:GetCanBeUsed(player, useSuccessTable)

    if GetAreEnemies(player, self) then
        useSuccessTable.useSuccess = false
    end

end
The difference being, in Cafu, the only thing implemented in the CompTeam file would be the variable TeamNum and the functions SetTeamNumber() and GetTeamNumber() in the component. The ability to set the number and name of teams etc, in Cafu, would be available in the lua script, it wouldn't be contained in the CompTeam like the NS2 mixin. It is only done in that way there because it is all in lua.

The mixin - component part is where I am currently getting stuck, as I need to learn C++ for these parts, but progress is being made, and my understanding is growing continuously, so I know I will get there eventually :D
User avatar
Carsten
Site Admin
Posts:2170
Joined:2004-08-19, 13:46
Location:Germany
Contact:

Re: Getting going after set-up

Post by Carsten » 2014-04-22, 14:01

Hi,

from what you have posted, it looks like you're on the right track, and creating a new component by copying e.g. ComponentBasicsT is definitively a good idea (I do it, too. :cheesy: )
Also check out the AllComponents.cpp file: new components must be included in the list in this file.

In any case, don't hesitate to post any questions you may have, I'm happy to help! (Pure C++ questions may yield better replies when posted to more C++ specific places, though.)

Regarding the Team Mixin code that you posted, I seem to understand it at a detail level, as the methods are simple enough, and I also agree that there is a suggestive analogy to components. (What I'm lacking is a higher-level understanding of that code, e.g. who calls these methods when in which context, but then this is probably not needed for our purposes.)

I think that your experience with NS2 can be a great help both for yourself when developing with Cafu, and also I would be very happy to learn if you ever hit a point where NS2 did allow you to do something that seems difficult or not possible in Cafu.

That being said, I would recommend that, when developing with Cafu, not to cling to NS2 too closely. Concepts are different, at least at a certain level, and things may work better if you not try to do things in Cafu like they're done in NS2. Starting the NS2 way may be a very good point for getting started, but eventually it is probably better to smoothly switch to the way things are done in Cafu. I'm sure that things are at least as easy and straightforward to implement in Cafu than they are in NS2, and if you ever hit a limit, I'm sure that we can overcome it.

Regarding C++ and Lua: In "pro C++" speak, all components are indeed C++ code.
However, by inherent default, all components have a representation as a Lua object, all component variables (of type TypeSys::VarT<...>) are automatically available to Lua scripts, many components call callbacks in Lua, almost all provide methods to be called from Lua, and most importantly, all components can "forward" their functionality to Lua, most prominently the Script component.

Script components can do anything that any other component can do, and act as a kind of "adapters": They forward their functionality to Lua scripts, that's their main job.
That feature can the most obviously be seen in the quasi trivial implementation of ComponentScriptT::DoServerFrame():

Code: Select all

void ComponentScriptT::DoServerFrame(float t)
{
    CallLuaMethod("Think", 0, "f", t);
}
With that, also consider the ComponentScriptT's DoSerialize() and DoDeserialize() methods: At this time, they only implement some auxiliary functionality (albeit an important one). But it is perfectly thinkable to expand this to serialize and deserialize variables that only exist in the Lua script that the component is reponsible for as well!

This feature of course needs a little more code, because we have to let the implementations of Do[De]Serialize() know which variables to act on, or alternatively have them call something like CallLuaMethod("Serialize", 0); and CallLuaMethod("Deserialize", 0);, which Lua scripts in turn had to implement if they wanted their variables to be (de-)serialized. Given all that, it seems to me that there is quasi no functionality that cannot be implemented in Lua. Being able to serialize and deserialize data that exists in Lua scripts but not in C++ seems to make it possible to implement arbitrary classes in Lua. :up:
Best regards,
Carsten
SoulRider
Posts:95
Joined:2014-04-06, 00:16

Re: Getting going after set-up

Post by SoulRider » 2014-04-24, 09:17

Carsten wrote: Also check out the AllComponents.cpp file: new components must be included in the list in this file.

In any case, don't hesitate to post any questions you may have, I'm happy to help! (Pure C++ questions may yield better replies when posted to more C++ specific places, though.)
Thanks for that. There is still a lot for me to learn about programming and languages, so my questions will probably be a mixture of not understanding engine, or not understanding the language. I probably won't know which is the issue, either :D
Carsten wrote:That being said, I would recommend that, when developing with Cafu, not to cling to NS2 too closely. Concepts are different, at least at a certain level, and things may work better if you not try to do things in Cafu like they're done in NS2. Starting the NS2 way may be a very good point for getting started, but eventually it is probably better to smoothly switch to the way things are done in Cafu. I'm sure that things are at least as easy and straightforward to implement in Cafu than they are in NS2, and if you ever hit a limit, I'm sure that we can overcome it.
Absolutely. I will try and use what I understand from NS2, and when that doesn't work, I will look at why and try to learn a new way to solve it. NS2 code is all in LUA, the engine isn't open source. To this end, I can make an entire game in Lua, and using the NS2 method wouldn't be an issue, except for performance overhead of course.
Carsten wrote:However, by inherent default, all components have a representation as a Lua object, all component variables (of type TypeSys::VarT<...>) are automatically available to Lua scripts, many components call callbacks in Lua, almost all provide methods to be called from Lua, and most importantly, all components can "forward" their functionality to Lua, most prominently the Script component.

Script components can do anything that any other component can do, and act as a kind of "adapters": They forward their functionality to Lua scripts, that's their main job.
That feature can the most obviously be seen in the quasi trivial implementation of ComponentScriptT::DoServerFrame():

Code: Select all

void ComponentScriptT::DoServerFrame(float t)
{
    CallLuaMethod("Think", 0, "f", t);
}
Totally, as per my last answer, the ability to run everything in lua gives me complete freedom. Most of my earlier questions were based around my confusion as to what a component was. Now I have figured it out, I have been able to feel more confident with the ideas I have :) Also, if you do at any point wish to understand a bit more of the NS2 code, I can always fill you in with my knowledge at any time.
Carsten wrote:With that, also consider the ComponentScriptT's DoSerialize() and DoDeserialize() methods: At this time, they only implement some auxiliary functionality (albeit an important one). But it is perfectly thinkable to expand this to serialize and deserialize variables that only exist in the Lua script that the component is reponsible for as well!

This feature of course needs a little more code, because we have to let the implementations of Do[De]Serialize() know which variables to act on, or alternatively have them call something like CallLuaMethod("Serialize", 0); and CallLuaMethod("Deserialize", 0);, which Lua scripts in turn had to implement if they wanted their variables to be (de-)serialized. Given all that, it seems to me that there is quasi no functionality that cannot be implemented in Lua. Being able to serialize and deserialize data that exists in Lua scripts but not in C++ seems to make it possible to implement arbitrary classes in Lua. :up:
This concept of serializing and deserializing is something which is new to me, and as yet, I must admit, I haven't tried to work out what it relates to. I will definitely need some guidance on this in the future :D
User avatar
Carsten
Site Admin
Posts:2170
Joined:2004-08-19, 13:46
Location:Germany
Contact:

Re: Getting going after set-up

Post by Carsten » 2014-04-26, 00:09

Hi SoulRider,

thanks for your update, it all sounds very well!

FYI, to get back to one of your earlier questions, I just started a new thread Creating a "binary release" from a compiled source code repo.

This is not yet the ultimate answer to everything, because it lacks, for example, zipping the created directory, FTP-uploading it, and several other features that the make_binary.py script has (which however is also quite inflexible), but I guess it's a quite useful first step with which you can relatively easily create redistributable packages (e.g. for other people to test and review) that don't contain any redundant files and whose programs start on double-click in Windows Explorer. :-)
Best regards,
Carsten
SoulRider
Posts:95
Joined:2014-04-06, 00:16

Re: Getting going after set-up

Post by SoulRider » 2014-04-26, 12:53

I've just read the thread, that is great :D It will be very helpful when trying to get testing done and content made :D
Post Reply

Who is online

Users browsing this forum: No registered users and 29 guests