Added Enigma game
This commit is contained in:
655
project/jni/application/enigma/src/player.cpp
Normal file
655
project/jni/application/enigma/src/player.cpp
Normal file
@@ -0,0 +1,655 @@
|
||||
/*
|
||||
* Copyright (C) 2002,2003,2004,2006 Daniel Heck
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
*/
|
||||
#include "player.hh"
|
||||
#include "Inventory.hh"
|
||||
#include "display.hh"
|
||||
#include "errors.hh"
|
||||
#include "sound.hh"
|
||||
#include "client.hh"
|
||||
#include "server.hh"
|
||||
#include "world.hh"
|
||||
#include "main.hh"
|
||||
|
||||
#include "ecl_util.hh"
|
||||
|
||||
#include <algorithm>
|
||||
#include <iostream>
|
||||
#include <cassert>
|
||||
|
||||
using namespace std;
|
||||
using namespace enigma;
|
||||
using namespace world;
|
||||
using world::Actor;
|
||||
using enigma::Inventory;
|
||||
|
||||
namespace
|
||||
{
|
||||
class PlayerInfo {
|
||||
public:
|
||||
PlayerInfo();
|
||||
|
||||
string name;
|
||||
Inventory inventory;
|
||||
vector<Actor*> actors;
|
||||
bool out_of_lives;
|
||||
double dead_dtime; // number of seconds the player is already dead
|
||||
bool inhibit_pickup;
|
||||
};
|
||||
|
||||
typedef vector<PlayerInfo> PlayerList;
|
||||
|
||||
struct RespawnInfo {
|
||||
RespawnInfo (Actor *a, double t)
|
||||
: actor(a), time_left(t)
|
||||
{}
|
||||
|
||||
Actor *actor; // The actor to respawn
|
||||
double time_left; // Time left before respawning
|
||||
};
|
||||
|
||||
|
||||
struct LevelLocalData {
|
||||
// Functions
|
||||
LevelLocalData() {}
|
||||
|
||||
void respawn_dead_actors(double dtime);
|
||||
void resurrect_actor (Actor *a);
|
||||
bool remove_extralife (Actor *a);
|
||||
|
||||
// Variables
|
||||
vector<RespawnInfo> respawn_list;
|
||||
};
|
||||
}
|
||||
|
||||
/* -------------------- PlayerInfo -------------------- */
|
||||
|
||||
PlayerInfo::PlayerInfo ()
|
||||
: name(),
|
||||
inventory(),
|
||||
actors(),
|
||||
out_of_lives(false),
|
||||
dead_dtime(0),
|
||||
inhibit_pickup(false)
|
||||
{}
|
||||
|
||||
/* -------------------- LevelLocalData -------------------- */
|
||||
|
||||
|
||||
void LevelLocalData::respawn_dead_actors(double dtime)
|
||||
{
|
||||
for (unsigned i=0; i<respawn_list.size(); ) {
|
||||
RespawnInfo &info = respawn_list[i];
|
||||
|
||||
info.time_left -= dtime;
|
||||
if (info.time_left < 0) {
|
||||
// if (lua::CallFunc (lua::LevelState(), "Respawn", Value()) != 0) {
|
||||
// throw enigma_levels::XLevelRuntime(string("Calling 'Respawn' failed:\n")+lua::LastError(lua::LevelState()));
|
||||
|
||||
info.actor->respawn();
|
||||
respawn_list.erase(respawn_list.begin()+i);
|
||||
continue; // don't increment i
|
||||
}
|
||||
++i;
|
||||
}
|
||||
}
|
||||
|
||||
void LevelLocalData::resurrect_actor (Actor *a)
|
||||
{
|
||||
const double RESPAWN_TIME = 1.5;
|
||||
|
||||
// a->find_respawnpos();
|
||||
world::SendMessage(a, "resurrect");
|
||||
remove_extralife(a);
|
||||
respawn_list.push_back(RespawnInfo(a, RESPAWN_TIME));
|
||||
}
|
||||
|
||||
bool LevelLocalData::remove_extralife (Actor *a)
|
||||
{
|
||||
Inventory *inv = player::GetInventory(a);
|
||||
int idx = inv->find("it-extralife");
|
||||
|
||||
if (idx == -1) // no extralife found
|
||||
return false;
|
||||
else {
|
||||
delete inv->yield_item(idx);
|
||||
player::RedrawInventory (inv);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* -------------------- Local variables -------------------- */
|
||||
|
||||
namespace
|
||||
{
|
||||
LevelLocalData leveldat;
|
||||
PlayerList players(2); // this currently has always size 2
|
||||
unsigned icurrent_player = 0;
|
||||
std::vector<Actor *> unassignedActors;
|
||||
}
|
||||
|
||||
|
||||
/* -------------------- Functions -------------------- */
|
||||
|
||||
void player::NewGame (bool isRestart) {
|
||||
int nplayers = 2; // Always prepare for two players
|
||||
std::vector<int> extralives(2);
|
||||
|
||||
// calculate number of extralives
|
||||
for (int i=0; i<nplayers; ++i) {
|
||||
if (isRestart) {
|
||||
// count existing number of extralives
|
||||
int idxLife = -1;
|
||||
extralives[i] = -1;
|
||||
do {
|
||||
++extralives[i];
|
||||
idxLife = players[i].inventory.find("it-extralife", ++idxLife);
|
||||
} while (idxLife != -1);
|
||||
} else {
|
||||
// new game provides 2 extralives
|
||||
extralives[i] = 2;
|
||||
}
|
||||
}
|
||||
|
||||
players.clear();
|
||||
players.resize(nplayers);
|
||||
|
||||
for (int i=0; i<nplayers; ++i) {
|
||||
Inventory *inv = GetInventory(i);
|
||||
|
||||
for (int j = 0 ; j < extralives[i]; j++)
|
||||
inv->add_item (MakeItem (world::it_extralife));
|
||||
}
|
||||
|
||||
unassignedActors.clear();
|
||||
}
|
||||
|
||||
void player::AddYinYang ()
|
||||
{
|
||||
for (unsigned i=0; i<players.size(); ++i) {
|
||||
Inventory *inv = GetInventory (i);
|
||||
if (inv->find ("it-yinyang") == -1)
|
||||
inv->add_item (world::MakeItem (world::it_yinyang));
|
||||
}
|
||||
}
|
||||
|
||||
void player::LevelLoaded(bool isRestart)
|
||||
{
|
||||
if (server::TwoPlayerGame && server::SingleComputerGame)
|
||||
AddYinYang();
|
||||
RedrawInventory ();
|
||||
}
|
||||
|
||||
void player::PrepareLevel()
|
||||
{
|
||||
// Clear up the inventories of all players: keep only extra lifes.
|
||||
for (unsigned iplayer=0; iplayer<players.size(); ++iplayer)
|
||||
{
|
||||
Inventory *inv = GetInventory(iplayer);
|
||||
int nextralifes=0;
|
||||
for (size_t i=0; i<inv->size(); ++i)
|
||||
if (get_id (inv->get_item(i)) == world::it_extralife)
|
||||
nextralifes += 1;
|
||||
inv->clear();
|
||||
for (int i=0; i<nextralifes; ++i)
|
||||
inv->add_item (world::MakeItem (world::it_extralife));
|
||||
|
||||
players[iplayer].actors.clear();
|
||||
}
|
||||
|
||||
SetCurrentPlayer(0);
|
||||
leveldat = LevelLocalData();
|
||||
}
|
||||
|
||||
void player::LevelFinished()
|
||||
{
|
||||
for (unsigned i=0; i<players.size(); ++i) {
|
||||
for (unsigned j=0; j<players[i].actors.size(); ++j) {
|
||||
Actor *a = players[i].actors[j];
|
||||
world::SendMessage(a, "disappear");
|
||||
world::KillRubberBands (a);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Inventory * player::GetInventory (int iplayer)
|
||||
{
|
||||
return &players[iplayer].inventory;
|
||||
}
|
||||
|
||||
|
||||
Inventory * player::GetInventory (Actor *a)
|
||||
{
|
||||
if (const Value *v = a->get_attrib("player"))
|
||||
return GetInventory(to_int(*v));
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
bool player::WieldedItemIs (Actor *a, const string &kind)
|
||||
{
|
||||
if (Inventory *inv = GetInventory(a))
|
||||
if (Item *it = inv->get_item(0))
|
||||
return it->is_kind(kind);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
int player::CurrentPlayer() {
|
||||
return icurrent_player;
|
||||
}
|
||||
|
||||
void player::SetCurrentPlayer(unsigned iplayer)
|
||||
{
|
||||
if (iplayer >= players.size())
|
||||
Log << ecl::strf("SetCurrentPlayer: no such player %d\n", iplayer);
|
||||
else {
|
||||
icurrent_player = iplayer;
|
||||
RedrawInventory (GetInventory(iplayer));
|
||||
}
|
||||
}
|
||||
|
||||
unsigned player::NumberOfRealPlayers() {
|
||||
unsigned real_players = 0;
|
||||
|
||||
for (unsigned i=0; i<players.size(); ++i) {
|
||||
if (!players[i].actors.empty()) {
|
||||
++real_players;
|
||||
}
|
||||
}
|
||||
|
||||
return real_players;
|
||||
}
|
||||
|
||||
/*! Sets respawn positions for black or white actors. */
|
||||
void player::SetRespawnPositions(GridPos pos, bool black)
|
||||
{
|
||||
ecl::V2 center = pos.center();
|
||||
|
||||
for (unsigned i=0; i<players.size(); ++i) {
|
||||
vector<Actor *> &al = players[i].actors;
|
||||
|
||||
for (unsigned j=0; j<al.size(); ++j) {
|
||||
const Value *val = al[j]->get_attrib(black ? "blackball" : "whiteball");
|
||||
if (val) {
|
||||
al[j]->set_respawnpos(center);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*! Remove respawn positions for black or white actors */
|
||||
void player::RemoveRespawnPositions(bool black) {
|
||||
for (unsigned i=0; i<players.size(); ++i) {
|
||||
vector<Actor *> &al = players[i].actors;
|
||||
|
||||
for (unsigned j=0; j<al.size(); ++j) {
|
||||
const Value *val = al[j]->get_attrib(black ? "blackball" : "whiteball");
|
||||
if (val) {
|
||||
al[j]->remove_respawnpos();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void player::Suicide()
|
||||
{
|
||||
for (unsigned i=0; i<players.size(); ++i) {
|
||||
vector<Actor *> &al = players[i].actors;
|
||||
for (unsigned j=0; j<al.size(); ++j) {
|
||||
world::SendMessage(al[j], "suicide");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Actor *player::ReplaceActor (unsigned iplayer, Actor *old, Actor *a)
|
||||
{
|
||||
if (iplayer >= players.size())
|
||||
server::RaiseError ("Invalid actor number");
|
||||
|
||||
vector<Actor *> &al = players[iplayer].actors;
|
||||
for (unsigned i=0; i<al.size(); ++i) {
|
||||
if (al[i] == old) {
|
||||
al[i] = a;
|
||||
return old;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
void player::AddActor (unsigned iplayer, Actor *a)
|
||||
{
|
||||
if (iplayer >= players.size())
|
||||
server::RaiseError ("Invalid actor number");
|
||||
|
||||
world::ReleaseActor(a);
|
||||
players[iplayer].actors.push_back(a);
|
||||
|
||||
if (players[iplayer].actors.size() == 1) {
|
||||
// the ``main actor'' was set
|
||||
client::Msg_PlayerPosition (iplayer, a->get_pos());
|
||||
}
|
||||
}
|
||||
|
||||
bool player::HasActor(unsigned iplayer, Actor *a) {
|
||||
if (iplayer >= 0 && iplayer < players.size()) {
|
||||
for (int i = 0; i < players[iplayer].actors.size(); i++) {
|
||||
if (players[iplayer].actors[i] == a)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void player::SwapPlayers()
|
||||
{
|
||||
if (NumberOfRealPlayers() >= 2) {
|
||||
SetCurrentPlayer(1-icurrent_player);
|
||||
}
|
||||
}
|
||||
|
||||
static bool has_extralive(unsigned pl, unsigned num) {
|
||||
size_t idx = 0;
|
||||
for (int i = 0; i < num; i++) {
|
||||
int idxLife = players[pl].inventory.find("it-extralife", idx);
|
||||
if (idxLife == -1)
|
||||
return false;
|
||||
else
|
||||
idx = idxLife + 1;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool resurrect_actor (unsigned pl, Actor *a)
|
||||
{
|
||||
assert(server::ConserveLevel); // no resurrection otherwise!
|
||||
|
||||
bool has_life = has_extralive(pl, 1);
|
||||
if (has_life)
|
||||
leveldat.resurrect_actor(a); // = resurrect with delay
|
||||
else
|
||||
players[pl].out_of_lives = true;
|
||||
return has_life;
|
||||
}
|
||||
|
||||
void player::AddUnassignedActor (Actor *a) {
|
||||
unassignedActors.push_back(a);
|
||||
}
|
||||
|
||||
static void CheckDeadActors()
|
||||
{
|
||||
bool toggle_player = false;
|
||||
const unsigned NO_PLAYER = UINT_MAX;
|
||||
unsigned toggle_to_player = NO_PLAYER;
|
||||
bool new_game = false; // complete restart (new lives)
|
||||
|
||||
// to live means to be not dead and to be able to move
|
||||
for (int pl = -1; pl<(int)players.size(); ++pl) { // -1 are unassigned actors
|
||||
vector<Actor*>& actors = (pl == -1) ? unassignedActors : players[pl].actors;
|
||||
bool has_living_actor = false; // controllable and living
|
||||
std::map<std::string, int> essMap;
|
||||
std::map<std::string, int>::iterator itEss;
|
||||
|
||||
for (size_t i=0; i<actors.size(); ++i) {
|
||||
Actor *a = actors[i];
|
||||
std::string essId;
|
||||
if (!a->string_attrib ("essential_id", &essId))
|
||||
essId = a->get_traits().name;
|
||||
int essential = a->int_attrib("essential");
|
||||
// count number of necessary actors per kind
|
||||
if (essential == 1)
|
||||
--essMap[essId];
|
||||
|
||||
if (!a->is_dead() ||
|
||||
(pl >= 0 && server::ConserveLevel && resurrect_actor(pl, a))) {
|
||||
// actor is still alive
|
||||
if (pl >= 0 && a->controlled_by(pl) && a->get_mouseforce() != 0.0) {
|
||||
has_living_actor = true;
|
||||
}
|
||||
// count number of alive actors per kind
|
||||
if (essential >= 0)
|
||||
++essMap[essId];
|
||||
}
|
||||
else {
|
||||
// player is dead and could not resurrect
|
||||
if (essential == -1) {
|
||||
// actor is personnally essential but dead
|
||||
new_game = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
// check if for any kind we have less living actors as required
|
||||
for (itEss = essMap.begin(); itEss != essMap.end(); itEss++) {
|
||||
if ((*itEss).second < 0)
|
||||
new_game = true;
|
||||
}
|
||||
|
||||
if (has_living_actor) {
|
||||
if (toggle_to_player == NO_PLAYER)
|
||||
toggle_to_player = pl;
|
||||
}
|
||||
else {
|
||||
if (pl == icurrent_player)
|
||||
// check if player has yinyang for single gamer mode
|
||||
if (player::GetInventory(pl)->find("it-yinyang",0) >= 0)
|
||||
toggle_player = true;
|
||||
else
|
||||
new_game = true;
|
||||
}
|
||||
}
|
||||
|
||||
// if no_living_player -> toggle_player is true and toggle_to_player is NO_PLAYER
|
||||
// => new_game is set to true below
|
||||
|
||||
if ((server::ConserveLevel == false) && !server::IsRestartingLevel() &&
|
||||
(new_game || (toggle_player && toggle_to_player == NO_PLAYER))) {
|
||||
// check if we have enough extralives for a restart instead of new game
|
||||
std::vector<int> numDead;
|
||||
bool reset_level = true;
|
||||
for (unsigned pl = 0; pl<players.size(); ++pl) {
|
||||
numDead.push_back(0);
|
||||
vector<Actor*>& actors = players[pl].actors;
|
||||
for (size_t i = 0; i<actors.size(); ++i) {
|
||||
Actor *a = actors[i];
|
||||
if (a->is_dead()) {
|
||||
numDead[pl]++;
|
||||
}
|
||||
}
|
||||
if (!has_extralive(pl, numDead[pl])) {
|
||||
reset_level = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (reset_level) {
|
||||
for (unsigned pl = 0; pl<players.size(); ++pl) {
|
||||
Inventory *inv = player::GetInventory(pl);
|
||||
for (int i=0; i<numDead[pl]; i++) {
|
||||
int idx = inv->find("it-extralife");
|
||||
ASSERT (idx != -1, XLevelRuntime, "Missing extralife for restart of level");
|
||||
delete inv->yield_item(idx);
|
||||
}
|
||||
}
|
||||
server::RestartLevel(); // should restart w/o scrolling
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (new_game) {
|
||||
server::Msg_RestartGame();
|
||||
}
|
||||
else if (toggle_player) {
|
||||
if (toggle_to_player == NO_PLAYER)
|
||||
server::Msg_RestartGame();
|
||||
else
|
||||
player::SetCurrentPlayer(toggle_to_player);
|
||||
}
|
||||
}
|
||||
|
||||
Actor *player::GetMainActor (unsigned iplayer)
|
||||
{
|
||||
vector<Actor *> &actors = players[iplayer].actors;
|
||||
return actors.empty() ? 0 : actors[0];
|
||||
}
|
||||
|
||||
void player::Tick(double dtime)
|
||||
{
|
||||
STATUSBAR->set_counter (server::GetMoveCounter());
|
||||
|
||||
|
||||
// Tell clients about position of main actor for stereo sound and
|
||||
// screen position
|
||||
for (unsigned iplayer = 0; iplayer < players.size(); ++iplayer) {
|
||||
if (Actor *ac = GetMainActor(iplayer))
|
||||
client::Msg_PlayerPosition (iplayer, ac->get_pos());
|
||||
}
|
||||
|
||||
// Respawn actors that have been dead for a certain amount of time
|
||||
leveldat.respawn_dead_actors(dtime);
|
||||
|
||||
// Update the respawn list or restart the game when all actors are
|
||||
// dead and no extra lifes are left.
|
||||
CheckDeadActors();
|
||||
}
|
||||
|
||||
void player::InhibitPickup(bool flag) {
|
||||
players[icurrent_player].inhibit_pickup = flag;
|
||||
}
|
||||
|
||||
/*! Return pointer to inventory if actor may pick up items, 0
|
||||
otherwise. */
|
||||
Inventory *player::MayPickup(Actor *a, Item *it)
|
||||
{
|
||||
int iplayer=-1;
|
||||
a->int_attrib("player", &iplayer);
|
||||
if (iplayer < 0 || (unsigned)iplayer >= players.size()) {
|
||||
// cerr << "PickupItem: illegal 'player' entry\n";
|
||||
return 0;
|
||||
}
|
||||
|
||||
Inventory *inv = GetInventory(iplayer);
|
||||
bool dont_pickup = players[iplayer].inhibit_pickup
|
||||
|| a->is_flying()
|
||||
|| !inv->willAddItem(it)
|
||||
|| a->is_dead();
|
||||
|
||||
return dont_pickup ? 0 : inv;
|
||||
}
|
||||
|
||||
void player::PickupItem (Actor *a, GridPos p)
|
||||
{
|
||||
if (Inventory *inv = MayPickup(a, GetField(p)->item)) {
|
||||
if (Item *item = world::YieldItem(p)) {
|
||||
item->on_pickup(a);
|
||||
inv->add_item(item);
|
||||
RedrawInventory (inv);
|
||||
sound::EmitSoundEvent ("pickup", p.center());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void player::PickupStoneAsItem (Actor *a, enigma::GridPos p)
|
||||
{
|
||||
if (Inventory *inv = MayPickup(a, GetField(p)->item))
|
||||
{
|
||||
if (world::Stone *stone = world::YieldStone(p))
|
||||
{
|
||||
string kind = stone->get_kind();
|
||||
if (kind[0] == 's')
|
||||
kind[0] = 'i';
|
||||
|
||||
if (Item *item = world::MakeItem(kind.c_str())) {
|
||||
KillRubberBands(stone);
|
||||
world::DisposeObject (stone);
|
||||
inv->add_item(item);
|
||||
player::RedrawInventory(inv);
|
||||
sound::EmitSoundEvent ("pickup", p.center());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void player::ActivateFirstItem()
|
||||
{
|
||||
Inventory &inv = players[icurrent_player].inventory;
|
||||
|
||||
|
||||
if (inv.size() > 0) {
|
||||
Item *it = inv.get_item (0);
|
||||
world::Actor *ac = 0;
|
||||
GridPos p;
|
||||
bool can_drop_item = false;
|
||||
if (!players[icurrent_player].actors.empty()) {
|
||||
ac = players[icurrent_player].actors[0];
|
||||
p = GridPos(ac->get_pos());
|
||||
can_drop_item = ac->can_drop_items();
|
||||
}
|
||||
|
||||
switch (it->activate(ac, p)) {
|
||||
case world::ITEM_DROP:
|
||||
// only drop if no item underneath and actor allows it
|
||||
if (it->can_drop_at(p) && can_drop_item) {
|
||||
it = inv.yield_first ();
|
||||
RedrawInventory (&inv);
|
||||
it->drop(ac, p);
|
||||
}
|
||||
break;
|
||||
case world::ITEM_KILL:
|
||||
DisposeObject (inv.yield_first ());
|
||||
RedrawInventory (&inv);
|
||||
break;
|
||||
case world::ITEM_KEEP:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void player::RotateInventory(int dir)
|
||||
{
|
||||
sound::EmitSoundEvent ("invrotate", ecl::V2());
|
||||
Inventory &inv = players[icurrent_player].inventory;
|
||||
if (dir == 1)
|
||||
inv.rotate_left ();
|
||||
else
|
||||
inv.rotate_right ();
|
||||
RedrawInventory (&inv);
|
||||
}
|
||||
|
||||
|
||||
/** Update the specified inventory on the screen, provided it is the
|
||||
inventory of the current player. For all other inventories, this
|
||||
function does nothing. */
|
||||
void player::RedrawInventory (Inventory *inv)
|
||||
{
|
||||
if (inv == GetInventory (CurrentPlayer()))
|
||||
RedrawInventory();
|
||||
}
|
||||
|
||||
|
||||
void player::RedrawInventory()
|
||||
{
|
||||
Inventory *inv = GetInventory (CurrentPlayer());
|
||||
std::vector<std::string> modelnames;
|
||||
for (size_t i=0; i<inv->size(); ++i) {
|
||||
world::Item *it = inv->get_item(i);
|
||||
modelnames.push_back(it->get_inventory_model());
|
||||
}
|
||||
STATUSBAR->set_inventory (modelnames);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user