COrder_{Move, Follow}
Fix typo with Unload.
This commit is contained in:
parent
1ef5a6d8bc
commit
77334d3533
7 changed files with 182 additions and 142 deletions
|
@ -41,46 +41,72 @@
|
|||
#include "unit.h"
|
||||
#include "unittype.h"
|
||||
#include "pathfinder.h"
|
||||
#include "map.h"
|
||||
#include "actions.h"
|
||||
#include "iolib.h"
|
||||
#include "script.h"
|
||||
|
||||
/*----------------------------------------------------------------------------
|
||||
-- Variables
|
||||
-- Functions
|
||||
----------------------------------------------------------------------------*/
|
||||
|
||||
/*----------------------------------------------------------------------------
|
||||
-- Function
|
||||
----------------------------------------------------------------------------*/
|
||||
|
||||
/**
|
||||
** Unit follow action:
|
||||
**
|
||||
** @param unit Pointer to unit.
|
||||
*/
|
||||
void HandleActionFollow(COrder& order, CUnit &unit)
|
||||
/* virtual */ void COrder_Follow::Save(CFile &file, const CUnit &unit) const
|
||||
{
|
||||
file.printf("{\"action-follow\",");
|
||||
|
||||
file.printf(" \"range\", %d,", this->Range);
|
||||
if (this->HasGoal()) {
|
||||
CUnit &goal = *this->GetGoal();
|
||||
if (goal.Destroyed) {
|
||||
/* this unit is destroyed so it's not in the global unit
|
||||
* array - this means it won't be saved!!! */
|
||||
printf ("FIXME: storing destroyed Goal - loading will fail.\n");
|
||||
}
|
||||
file.printf(" \"goal\", \"%s\",", UnitReference(goal).c_str());
|
||||
}
|
||||
file.printf(" \"tile\", {%d, %d},", this->goalPos.x, this->goalPos.y);
|
||||
|
||||
file.printf(" \"state\", %d,", this->State);
|
||||
SaveDataMove(file);
|
||||
|
||||
file.printf("}");
|
||||
}
|
||||
|
||||
/* virtual */ bool COrder_Follow::ParseSpecificData(lua_State *l, int &j, const char *value, const CUnit &unit)
|
||||
{
|
||||
if (ParseMoveData(l, j, value)) {
|
||||
return true;
|
||||
} else if (!strcmp(value, "state")) {
|
||||
++j;
|
||||
lua_rawgeti(l, -1, j + 1);
|
||||
this->State = LuaToNumber(l, -1);
|
||||
lua_pop(l, 1);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/* virtual */ bool COrder_Follow::Execute(CUnit &unit)
|
||||
{
|
||||
if (unit.Wait) {
|
||||
unit.Wait--;
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
CUnit *goal = order.GetGoal();
|
||||
CUnit *goal = this->GetGoal();
|
||||
|
||||
// Reached target
|
||||
if (order.SubAction.Follow == 128) {
|
||||
if (this->State == 128) {
|
||||
|
||||
if (!goal || !goal->IsVisibleAsGoal(*unit.Player)) {
|
||||
DebugPrint("Goal gone\n");
|
||||
order.ClearGoal();
|
||||
unit.ClearAction();
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (goal->tilePos == order.goalPos) {
|
||||
if (goal->tilePos == this->goalPos) {
|
||||
// Move to the next order
|
||||
if (unit.Orders.size() > 1) {
|
||||
order.ClearGoal();
|
||||
unit.ClearAction();
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Reset frame to still frame while we wait
|
||||
|
@ -88,32 +114,30 @@ void HandleActionFollow(COrder& order, CUnit &unit)
|
|||
unit.Frame = unit.Type->StillFrame;
|
||||
UnitUpdateHeading(unit);
|
||||
unit.Wait = 10;
|
||||
if (order.Range > 1) {
|
||||
order.Range = 1;
|
||||
order.SubAction.Follow = 0;
|
||||
if (this->Range > 1) {
|
||||
this->Range = 1;
|
||||
this->State = 0;
|
||||
}
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
order.SubAction.Follow = 0;
|
||||
this->State = 0;
|
||||
}
|
||||
if (!order.SubAction.Follow) { // first entry
|
||||
order.SubAction .Follow= 1;
|
||||
order.NewResetPath();
|
||||
Assert(unit.State == 0);
|
||||
if (!this->State) { // first entry
|
||||
this->State = 1;
|
||||
this->NewResetPath();
|
||||
}
|
||||
switch (DoActionMove(unit)) { // reached end-point?
|
||||
case PF_UNREACHABLE:
|
||||
// Some tries to reach the goal
|
||||
if (order.CheckRange()) {
|
||||
order.Range++;
|
||||
if (this->CheckRange()) {
|
||||
this->Range++;
|
||||
break;
|
||||
}
|
||||
// FALL THROUGH
|
||||
case PF_REACHED:
|
||||
{
|
||||
if (!goal) { // goal has died
|
||||
unit.ClearAction();
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
// Handle Teleporter Units
|
||||
// FIXME: BAD HACK
|
||||
|
@ -132,8 +156,6 @@ void HandleActionFollow(COrder& order, CUnit &unit)
|
|||
unit.tilePos.x * PixelTileSize.x + PixelTileSize.x / 2,
|
||||
unit.tilePos.y * PixelTileSize.y + PixelTileSize.y / 2);
|
||||
#endif
|
||||
unit.ClearAction();
|
||||
|
||||
// FIXME: we must check if the units supports the new order.
|
||||
CUnit &dest = *goal->Goal;
|
||||
|
||||
|
@ -141,26 +163,24 @@ void HandleActionFollow(COrder& order, CUnit &unit)
|
|||
|| (dest.NewOrder->Action == UnitActionResource && !unit.Type->Harvester)
|
||||
|| (dest.NewOrder->Action == UnitActionAttack && !unit.Type->CanAttack)
|
||||
|| (dest.NewOrder->Action == UnitActionBoard && unit.Type->UnitType != UnitTypeLand)) {
|
||||
unit.ClearAction();
|
||||
unit.CurrentOrder()->ClearGoal();
|
||||
return true;
|
||||
} else {
|
||||
if (dest.NewOrder->HasGoal()) {
|
||||
if (dest.NewOrder->GetGoal()->Destroyed) {
|
||||
// FIXME: perhaps we should use another dest?
|
||||
DebugPrint("Destroyed unit in teleport unit\n");
|
||||
dest.NewOrder->ClearGoal();
|
||||
dest.NewOrder->Action = UnitActionStill;
|
||||
delete dest.NewOrder;
|
||||
dest.NewOrder = NULL;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
delete unit.CurrentOrder();
|
||||
unit.Orders[0] = dest.NewOrder->Clone();
|
||||
unit.CurrentResource = dest.CurrentResource;
|
||||
return false;
|
||||
}
|
||||
return;
|
||||
}
|
||||
order.goalPos = goal->tilePos;
|
||||
order.SubAction.Follow = 128;
|
||||
this->goalPos = goal->tilePos;
|
||||
this->State = 128;
|
||||
}
|
||||
// FALL THROUGH
|
||||
default:
|
||||
|
@ -170,36 +190,43 @@ void HandleActionFollow(COrder& order, CUnit &unit)
|
|||
// Target destroyed?
|
||||
if (goal && !goal->IsVisibleAsGoal(*unit.Player)) {
|
||||
DebugPrint("Goal gone\n");
|
||||
order.goalPos = goal->tilePos + goal->Type->GetHalfTileSize();
|
||||
order.ClearGoal();
|
||||
this->goalPos = goal->tilePos + goal->Type->GetHalfTileSize();
|
||||
this->ClearGoal();
|
||||
goal = NoUnitP;
|
||||
order.NewResetPath();
|
||||
this->NewResetPath();
|
||||
}
|
||||
|
||||
if (!unit.Anim.Unbreakable) {
|
||||
// If our leader is dead or stops or attacks:
|
||||
// Attack any enemy in reaction range.
|
||||
// If don't set the goal, the unit can than choose a
|
||||
// better goal if moving nearer to enemy.
|
||||
if (unit.Type->CanAttack
|
||||
&& (!goal || goal->CurrentAction() == UnitActionAttack || goal->CurrentAction() == UnitActionStill)) {
|
||||
goal = AttackUnitsInReactRange(unit);
|
||||
if (goal) {
|
||||
// Save current command to come back.
|
||||
COrder *savedOrder = order.Clone();
|
||||
if (unit.Anim.Unbreakable) {
|
||||
return false;
|
||||
}
|
||||
// If our leader is dead or stops or attacks:
|
||||
// Attack any enemy in reaction range.
|
||||
// If don't set the goal, the unit can than choose a
|
||||
// better goal if moving nearer to enemy.
|
||||
if (unit.Type->CanAttack
|
||||
&& (!goal || goal->CurrentAction() == UnitActionAttack || goal->CurrentAction() == UnitActionStill)) {
|
||||
CUnit *target = AttackUnitsInReactRange(unit);
|
||||
if (target) {
|
||||
// Save current command to come back.
|
||||
COrder *savedOrder = this->Clone();
|
||||
|
||||
CommandAttack(unit, goal->tilePos, NULL, FlushCommands);
|
||||
CommandAttack(unit, target->tilePos, NULL, FlushCommands);
|
||||
|
||||
if (unit.StoreOrder(savedOrder) == false) {
|
||||
delete savedOrder;
|
||||
savedOrder = NULL;
|
||||
}
|
||||
// This stops the follow command and the attack is executed
|
||||
unit.CurrentOrder()->ClearGoal();
|
||||
unit.ClearAction();
|
||||
if (unit.StoreOrder(savedOrder) == false) {
|
||||
delete savedOrder;
|
||||
savedOrder = NULL;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void HandleActionFollow(COrder& order, CUnit &unit)
|
||||
{
|
||||
if (order.Execute(unit)) {
|
||||
unit.ClearAction();
|
||||
}
|
||||
}
|
||||
|
||||
//@}
|
||||
|
|
|
@ -37,27 +37,43 @@
|
|||
#include <stdlib.h>
|
||||
|
||||
#include "stratagus.h"
|
||||
#include "video.h"
|
||||
|
||||
#include "actions.h"
|
||||
#include "unittype.h"
|
||||
#include "animation.h"
|
||||
#include "player.h"
|
||||
#include "unit.h"
|
||||
#include "tileset.h"
|
||||
#include "map.h"
|
||||
#include "actions.h"
|
||||
#include "pathfinder.h"
|
||||
#include "sound.h"
|
||||
#include "interface.h"
|
||||
#include "map.h"
|
||||
#include "ai.h"
|
||||
#include "iolib.h"
|
||||
#include "script.h"
|
||||
|
||||
/*----------------------------------------------------------------------------
|
||||
-- Variables
|
||||
-- Functions
|
||||
----------------------------------------------------------------------------*/
|
||||
|
||||
/*----------------------------------------------------------------------------
|
||||
-- Function
|
||||
----------------------------------------------------------------------------*/
|
||||
|
||||
/* virtual */ void COrder_Move::Save(CFile &file, const CUnit &unit) const
|
||||
{
|
||||
file.printf("{\"action-move\",");
|
||||
|
||||
file.printf(" \"range\", %d,", this->Range);
|
||||
file.printf(" \"tile\", {%d, %d},", this->goalPos.x, this->goalPos.y);
|
||||
|
||||
SaveDataMove(file);
|
||||
file.printf("}");
|
||||
}
|
||||
|
||||
/* virtual */ bool COrder_Move::ParseSpecificData(lua_State *l, int &j, const char *value, const CUnit &unit)
|
||||
{
|
||||
if (ParseMoveData(l, j, value)) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
** Unit moves! Generic function called from other actions.
|
||||
|
@ -72,16 +88,17 @@ int DoActionMove(CUnit &unit)
|
|||
Vec2i posd; // movement in tile.
|
||||
int d;
|
||||
Vec2i pos;
|
||||
int move;
|
||||
int off;
|
||||
|
||||
Assert(unit.CanMove());
|
||||
if (!unit.Moving &&
|
||||
(unit.Type->Animations->Move != unit.Anim.CurrAnim || !unit.Anim.Wait)) {
|
||||
|
||||
COrder& order = *unit.CurrentOrder();
|
||||
|
||||
if (!unit.Moving && (unit.Type->Animations->Move != unit.Anim.CurrAnim || !unit.Anim.Wait)) {
|
||||
Assert(!unit.Anim.Unbreakable);
|
||||
|
||||
// FIXME: So units flying up and down are not affected.
|
||||
unit.IX = unit.IY = 0;
|
||||
unit.IX = 0;
|
||||
unit.IY = 0;
|
||||
|
||||
UnmarkUnitFieldFlags(unit);
|
||||
d = NextPathElement(unit, &posd.x, &posd.y);
|
||||
|
@ -110,14 +127,14 @@ int DoActionMove(CUnit &unit)
|
|||
break;
|
||||
}
|
||||
pos = unit.tilePos;
|
||||
off = unit.Offset;
|
||||
int off = unit.Offset;
|
||||
//
|
||||
// Transporter (un)docking?
|
||||
//
|
||||
// FIXME: This is an ugly hack
|
||||
if (unit.Type->CanTransport() &&
|
||||
((Map.WaterOnMap(off) && Map.CoastOnMap(pos + posd)) ||
|
||||
(Map.CoastOnMap(off) && Map.WaterOnMap(pos + posd)))) {
|
||||
if (unit.Type->CanTransport()
|
||||
&& ((Map.WaterOnMap(off) && Map.CoastOnMap(pos + posd))
|
||||
|| (Map.CoastOnMap(off) && Map.WaterOnMap(pos + posd)))) {
|
||||
PlayUnitSound(unit, VoiceDocking);
|
||||
}
|
||||
|
||||
|
@ -142,12 +159,11 @@ int DoActionMove(CUnit &unit)
|
|||
} else {
|
||||
posd.x = Heading2X[unit.Direction / NextDirection];
|
||||
posd.y = Heading2Y[unit.Direction / NextDirection];
|
||||
d = unit.CurrentOrder()->Data.Move.Length + 1;
|
||||
d = order.Data.Move.Length + 1;
|
||||
}
|
||||
|
||||
unit.CurrentOrder()->Data.Move.Cycles++;//reset have to be manualy controled by caller.
|
||||
move = UnitShowAnimationScaled(unit, unit.Type->Animations->Move,
|
||||
Map.Field(unit.Offset)->Cost);
|
||||
order.Data.Move.Cycles++;//reset have to be manualy controled by caller.
|
||||
int move = UnitShowAnimationScaled(unit, unit.Type->Animations->Move, Map.Field(unit.Offset)->Cost);
|
||||
|
||||
unit.IX += posd.x * move;
|
||||
unit.IY += posd.y * move;
|
||||
|
@ -161,58 +177,37 @@ int DoActionMove(CUnit &unit)
|
|||
return d;
|
||||
}
|
||||
|
||||
/**
|
||||
** Unit move action:
|
||||
**
|
||||
** Move to a place or to a unit (can move).
|
||||
** Tries 10x to reach the target, note this are the complete tries.
|
||||
** If the target entered another unit, move to it's position.
|
||||
** If the target unit is destroyed, continue to move to it's last position.
|
||||
**
|
||||
** @param unit Pointer to unit.
|
||||
*/
|
||||
void HandleActionMove(COrder& order, CUnit &unit)
|
||||
{
|
||||
CUnit *goal;
|
||||
|
||||
/* virtual */ bool COrder_Move::Execute(CUnit &unit)
|
||||
{
|
||||
Assert(unit.CanMove());
|
||||
|
||||
if (unit.Wait) {
|
||||
unit.Wait--;
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
// FIXME: (mr-russ) Make a reachable goal here with GoalReachable ...
|
||||
|
||||
switch (DoActionMove(unit)) { // reached end-point?
|
||||
case PF_UNREACHABLE:
|
||||
//
|
||||
// Some tries to reach the goal
|
||||
//
|
||||
if (order.CheckRange()) {
|
||||
order.Range++;
|
||||
if (this->CheckRange()) {
|
||||
this->Range++;
|
||||
break;
|
||||
}
|
||||
// FALL THROUGH
|
||||
case PF_REACHED:
|
||||
// Release target, if any.
|
||||
order.ClearGoal();
|
||||
unit.ClearAction();
|
||||
return;
|
||||
|
||||
return true;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
//
|
||||
// Target destroyed?
|
||||
//
|
||||
goal = order.GetGoal();
|
||||
if (goal && goal->Destroyed) {
|
||||
DebugPrint("Goal dead\n");
|
||||
order.goalPos = goal->tilePos + goal->Type->GetHalfTileSize();
|
||||
order.ClearGoal();
|
||||
order.NewResetPath();
|
||||
void HandleActionMove(COrder& order, CUnit &unit)
|
||||
{
|
||||
if (order.Execute(unit)) {
|
||||
unit.ClearAction();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -63,7 +63,7 @@
|
|||
file.printf(" \"goal\", \"%s\",", UnitReference(goal).c_str());
|
||||
}
|
||||
file.printf(" \"tile\", {%d, %d}, ", this->goalPos.x, this->goalPos.y);
|
||||
file.printf("\"state\", %d,\n ");
|
||||
file.printf("\"state\", %d,\n ", this->State);
|
||||
SaveDataMove(file);
|
||||
file.printf("}");
|
||||
}
|
||||
|
|
|
@ -192,7 +192,7 @@ unsigned SyncHash; /// Hash calculated to find sync failures
|
|||
|
||||
/* static */ COrder* COrder::NewActionFollow(CUnit &dest)
|
||||
{
|
||||
COrder *order = new COrder(UnitActionFollow);
|
||||
COrder_Follow *order = new COrder_Follow;
|
||||
|
||||
// Destination could be killed.
|
||||
// Should be handled in action, but is not possible!
|
||||
|
@ -210,7 +210,9 @@ unsigned SyncHash; /// Hash calculated to find sync failures
|
|||
|
||||
/* static */ COrder* COrder::NewActionMove(const Vec2i &pos)
|
||||
{
|
||||
COrder *order = new COrder(UnitActionMove);
|
||||
Assert(Map.Info.IsPointOnMap(pos));
|
||||
|
||||
COrder_Move *order = new COrder_Move;
|
||||
|
||||
order->goalPos = pos;
|
||||
|
||||
|
|
|
@ -171,11 +171,8 @@ public:
|
|||
#if 1 // currently needed for parsing
|
||||
static COrder* NewActionAttack() { return new COrder(UnitActionAttack); }
|
||||
static COrder* NewActionAttackGround() { return new COrder(UnitActionAttackGround); }
|
||||
static COrder* NewActionFollow() { return new COrder(UnitActionFollow); }
|
||||
static COrder* NewActionMove() { return new COrder(UnitActionMove); }
|
||||
static COrder* NewActionResource() { return new COrder(UnitActionResource); }
|
||||
static COrder* NewActionReturnGoods() { return new COrder(UnitActionReturnGoods); }
|
||||
static COrder* NewActionUnload() { return new COrder(UnitActionUnload); }
|
||||
#endif
|
||||
|
||||
private:
|
||||
|
@ -200,7 +197,6 @@ public:
|
|||
|
||||
union {
|
||||
int Attack;
|
||||
int Follow;
|
||||
int Res;
|
||||
} SubAction;
|
||||
|
||||
|
@ -319,6 +315,35 @@ public:
|
|||
virtual bool Execute(CUnit &unit);
|
||||
};
|
||||
|
||||
class COrder_Follow : public COrder
|
||||
{
|
||||
public:
|
||||
COrder_Follow() : COrder(UnitActionFollow), State(0) {}
|
||||
|
||||
virtual COrder_Follow *Clone() const { return new COrder_Follow(*this); }
|
||||
|
||||
virtual void Save(CFile &file, const CUnit &unit) const;
|
||||
virtual bool ParseSpecificData(lua_State *l, int &j, const char *value, const CUnit &unit);
|
||||
|
||||
virtual bool Execute(CUnit &unit);
|
||||
private:
|
||||
unsigned int State;
|
||||
};
|
||||
|
||||
class COrder_Move : public COrder
|
||||
{
|
||||
public:
|
||||
COrder_Move() : COrder(UnitActionMove) {}
|
||||
|
||||
virtual COrder_Move *Clone() const { return new COrder_Move(*this); }
|
||||
|
||||
virtual void Save(CFile &file, const CUnit &unit) const;
|
||||
virtual bool ParseSpecificData(lua_State *l, int &j, const char *value, const CUnit &unit);
|
||||
|
||||
virtual bool Execute(CUnit &unit);
|
||||
};
|
||||
|
||||
|
||||
|
||||
class COrder_Patrol : public COrder
|
||||
{
|
||||
|
|
|
@ -257,7 +257,7 @@ bool COrder::ParseSpecificData(lua_State *l, int &j, const char *value, const CU
|
|||
} else if (!strcmp(value, "subaction")) {
|
||||
++j;
|
||||
lua_rawgeti(l, -1, j + 1);
|
||||
this->SubAction.Attack = this->SubAction.Follow = this->SubAction.Res = LuaToNumber(l, -1);
|
||||
this->SubAction.Attack = this->SubAction.Res = LuaToNumber(l, -1);
|
||||
lua_pop(l, 1);
|
||||
} else if (!strcmp(value, "current-resource")) {
|
||||
++j;
|
||||
|
@ -335,9 +335,9 @@ void CclParseOrder(lua_State *l, const CUnit &unit, COrderPtr *orderPtr)
|
|||
} else if (!strcmp(actiontype, "action-stand-ground")) {
|
||||
*orderPtr = new COrder_Still(true);
|
||||
} else if (!strcmp(actiontype, "action-follow")) {
|
||||
*orderPtr = COrder::NewActionFollow();
|
||||
*orderPtr = new COrder_Follow;
|
||||
} else if (!strcmp(actiontype, "action-move")) {
|
||||
*orderPtr = COrder::NewActionMove();
|
||||
*orderPtr = new COrder_Move;
|
||||
} else if (!strcmp(actiontype, "action-attack")) {
|
||||
*orderPtr = COrder::NewActionAttack();
|
||||
} else if (!strcmp(actiontype, "action-attack-ground")) {
|
||||
|
@ -357,7 +357,7 @@ void CclParseOrder(lua_State *l, const CUnit &unit, COrderPtr *orderPtr)
|
|||
} else if (!strcmp(actiontype, "action-board")) {
|
||||
*orderPtr = new COrder_Board;
|
||||
} else if (!strcmp(actiontype, "action-unload")) {
|
||||
*orderPtr = COrder::NewActionUnload();
|
||||
*orderPtr = new COrder_Unload;
|
||||
} else if (!strcmp(actiontype, "action-patrol")) {
|
||||
*orderPtr = new COrder_Patrol;
|
||||
} else if (!strcmp(actiontype, "action-build")) {
|
||||
|
|
|
@ -96,12 +96,6 @@ void SaveOrder(const COrder &order, const CUnit &unit, CFile *file)
|
|||
case UnitActionNone:
|
||||
file.printf("\"action-none\",");
|
||||
break;
|
||||
case UnitActionFollow:
|
||||
file.printf("\"action-follow\",");
|
||||
break;
|
||||
case UnitActionMove:
|
||||
file.printf("\"action-move\",");
|
||||
break;
|
||||
case UnitActionAttack:
|
||||
file.printf("\"action-attack\",");
|
||||
break;
|
||||
|
@ -138,9 +132,6 @@ void SaveOrder(const COrder &order, const CUnit &unit, CFile *file)
|
|||
case UnitActionAttackGround:
|
||||
file.printf(", \"subaction\", %d", order.SubAction.Attack);
|
||||
break;
|
||||
case UnitActionFollow:
|
||||
file.printf(", \"subaction\", %d", order.SubAction.Follow);
|
||||
break;
|
||||
case UnitActionResource:
|
||||
case UnitActionReturnGoods:
|
||||
file.printf(", \"subaction\", %d", order.SubAction.Res);
|
||||
|
|
Loading…
Add table
Reference in a new issue