Part 26: Alcoves and torch holders
Downloads
Objects on the walls
class CObjectStack
{
[...]
CVec2 mPos;
EWallSide mSide;
[...]
};
We will use this convention:
CObjectStack* addObjectsStack(CVec2 pos, EWallSide side);
CObjectStack* findObjectsStack(CVec2 pos, EWallSide side);
void removeObjectsStack(CVec2 pos, EWallSide side);
This is not a huge work, as all these functions mostly rely on the findObjectsStackIndex() one, and it's easy to modify
int CMap::findObjectsStackIndex(CVec2 pos, EWallSide side)
{
for (size_t i = 0; i < mObjectsStacks.size(); ++i)
if (mObjectsStacks[i].mPos == pos && mObjectsStacks[i].mSide == side)
return i;
return -1;
}
And obviously, the format of the map file will change a little bit to store this side and the new types of walls we will add.
The alcove wall
<wall name="Alcove">
<image>Alcove.png</image>
<param type="enum" values="Arched;Square;Vi Altar">Type</param>
<param type="stack"/>
</wall>
There are 3 types of alcoves in this game:
<ornate name="Alcove_Arched">
<image_front>Alcove_Arched_front.png</image_front>
<image_side>Alcove_Arched_side.png</image_side>
<pos_front x="64" y="69"/>
<pos_side x="34" y="69"/>
</ornate>
<ornate name="Alcove_Square">
<image_front>Alcove_Square_front.png</image_front>
<image_side>Alcove_Square_side.png</image_side>
<pos_front x="67" y="69"/>
<pos_side x="38" y="70"/>
</ornate>
<ornate name="Alcove_Vi">
<image_front>Alcove_Vi_front.png</image_front>
<image_side>Alcove_Vi_side.png</image_side>
<pos_front x="64" y="69"/>
<pos_side x="36" y="70"/>
</ornate>
The second parameter of this type of wall - "stack" - is a sort of "fake" parameter.
Alcoves in the editor
void CEditor::addWallParamsItems(uint8_t type)
{
[...]
std::vector<CParamType>& sourceList = map.mWallsParams[type];
for (size_t i = 0; i < sourceList.size(); ++i)
{
[...]
else if (sourceList[i].mType == eParamEnum)
{
CParamEnum* par = (CParamEnum*)param;
addLabel(wallSelGrid, sourceList[i].mName + ":", &mWallParamItems);
addComboBox(wallSelGrid, sourceList[i].mValues, &mWallParamItems, i, par->mValue);
}
else if (sourceList[i].mType == eParamStack)
{
CObjectStack* stack = map.findObjectsStack(pos, side);
size_t size = 0;
if (stack != NULL)
{
for (size_t i = 0; i < stack->getSize(); ++i)
{
addButton(wallSelGrid, "delete", &mWallParamItems, i);
addComboBox(wallSelGrid, bridge.itemsList, &mWallParamItems, sourceList.size() - 1 + i, stack->getObject(i).getType());
}
size = stack->getSize();
}
addButton(wallSelGrid, "add", &mWallParamItems, size);
}
}
}
In CBridge::setSelParamCombo() we handle the changes of the combo boxes.
void CBridge::setSelParamCombo(qint32 id, qint32 value)
{
[...]
else if (mTabIndex == eTabWalls)
{
CVec2 pos = editor.mSelectStart / TILE_SIZE;
EWallSide side = editor.getWallSideAbs(editor.mSelectStart);
CWall* wall = &map.getTile(pos)->mWalls[side];
bool isStack = false;
if ((size_t)id >= wall->mParams.size())
isStack = true;
else if (wall->mParams[id]->mType == eParamStack)
isStack = true;
if (isStack == true)
{
CObjectStack* stack = map.findObjectsStack(pos, side);
if (stack != NULL)
{
CObject& object = stack->getObject(id - (wall->mParams.size() - 1));
object.setType(value);
}
}
else
{
CParam* param = wall->mParams[id];
switch (param->mType)
{
SET_COMBO(eParamOrnate, CParamOrnate)
SET_COMBO(eParamEnum, CParamEnum)
default:
break;
}
}
}
[...]
}
And in CBridge::selButtonClicked() we handle the "add" and "delete" buttons:
void CBridge::selButtonClicked(qint32 id)
{
if (id == -1)
return;
if (mTabIndex == eTabObjects)
{
[...]
}
else if (mTabIndex == eTabWalls)
{
CVec2 pos = editor.mSelectStart / TILE_SIZE;
EWallSide side = editor.getWallSideAbs(editor.mSelectStart);
CObjectStack* stack = map.findObjectsStack(pos, side);
if (stack == NULL || (size_t)id == stack->getSize())
{
// add new object
if (stack == NULL)
stack = map.addObjectsStack(pos, side);
CObject object;
stack->addObject(object);
}
else
{
// remove object
stack->removeObject(id);
if (stack->getSize() == 0)
map.removeObjectsStack(pos, side);
}
// update the buttons
QMetaObject::invokeMethod(this, "updateSelStack", Qt::QueuedConnection);
}
}
There is also a lot of changes in "clipboard.cpp" and "tools.cpp" to add the walls' stacks in the copy, cut and paste tools.
Alcoves in the game
else if (wall->getType() == eWallAlcove)
{
//------------------------------------------------------------------------------
// alcove
int type = wall->getEnumParam("Type");
walls.drawOrnate(image, tablePos, side, ORNATE_ARCHED_ALCOVE + type);
if (tablePos.y > 0 && side == eWallSideUp)
{
objects.drawObjectsStack(image, mapPos, playerSide, tablePos);
if (tablePos == CVec2(2, 3) && mouse.mObjectInHand.getType() != 0)
{
CRect rectLeft(CVec2(66, 84), CVec2(157, 124));
mouse.addArea(eMouseAreaG_DropObject, rectLeft, eCursor_Hand, (void*)mapPos.x, (void*)mapPos.y, (void*)playerSide);
}
}
}
Note that the objects are drawn only when the wall is facing us.
void CObjects::drawObjectsStack(QImage* image, CVec2 mapPos, EWallSide side, CVec2 tablePos)
{
[...]
CObjectStack* stack = map.findObjectsStack(mapPos, side);
if (stack != NULL)
{
for (size_t i = 0; i < stack->getSize(); ++i)
{
int type = stack->getObject(i).getType();
if (type != 0)
{
// get object image
[...]
// get object position and add a pseudo random value
CVec2 pos;
if (side == eWallSideMax)
pos = getObjectPos(tablePos);
else
pos = getAlcovePos(tablePos);
[...]
// compute the scale based on the reference position (the nearest)
CVec2 pos0, pos1;
float scale;
if (side == eWallSideMax)
{
pos0 = getObjectPos(CVec2(WALL_TABLE_WIDTH, (WALL_TABLE_HEIGHT - 1) * 2));
pos1 = getObjectPos(CVec2(WALL_TABLE_WIDTH, tablePos.y));
scale = (float)(pos1.x - 112) / (float)(pos0.x - 112);
}
else
{
static float scalesTable[] = {0.36, 0.53, 0.82};
scale = scalesTable[tablePos.y - 1];
}
// scale the object in a temporary image
[...]
// darken the object based on it's distance
float shadow;
if (side == eWallSideMax)
shadow = ((WALL_TABLE_HEIGHT - 1) * 2 - tablePos.y) * 0.13f;
else
shadow = ((WALL_TABLE_HEIGHT - 1) * 2 - tablePos.y * 2) * 0.13f;
graph2D.darken(&scaledObject, shadow);
// draw the object on screen
[...]
// add the mouse area
if (mouse.mObjectInHand.getType() == 0)
{
bool isAreaOK = false;
if (side == eWallSideMax)
{
if (tablePos.x >= WALL_TABLE_WIDTH - 1 && tablePos.x <= WALL_TABLE_WIDTH)
{
if (tablePos.y == (WALL_TABLE_HEIGHT - 1) * 2 ||
(tablePos.y == (WALL_TABLE_HEIGHT - 2) * 2 + 1 && isWallInFront() == false))
{
isAreaOK = true;
}
}
}
else
{
if (tablePos.x == WALL_TABLE_WIDTH / 2 && tablePos.y == WALL_TABLE_HEIGHT - 1)
isAreaOK = true;
}
if (isAreaOK)
{
CRect mouseRect(pos, CVec2(pos.x + scaledObject.width() - 1, pos.y + scaledObject.height() - 1));
mouse.addArea(eMouseAreaG_PickObject, mouseRect, eCursor_Hand, (void*)stack, (void*)i);
}
}
}
}
}
}
The position of the object on the screen is computed by a new function:
CVec2 CObjects::getAlcovePos(CVec2 tablePos)
{
CVec2 pos;
pos.x = 250 * (tablePos.x * 2 - 4) / (8.5 - tablePos.y * 2) + 112;
static int yTable[] = {91, 104, 120};
pos.y = yTable[tablePos.y - 1];
return pos;
}
As you can see, for the mouse areas I used the same identifiers as for the ground stacks - eMouseAreaG_PickObject and
Torch holders in the editor
<wall name="Torch">
<image>Torch.png</image>
<param type="bool">isEmpty</param>
</wall>
...and how it appears in the editor:
Torch holders in the game
void CWalls::init()
{
// create stacks for wall torches
for (int y = 0; y < map.mSize.y; ++y)
for (int x = 0; x < map.mSize.x; ++x)
{
CVec2 pos(x, y);
CTile* t = map.getTile(pos);
for (int side = 0; side < eWallSideMax; ++side)
{
CWall* w = &t->mWalls[side];
if (w->getType() == eWallTorch)
{
bool isEmpty = w->getBoolParam("isEmpty");
map.removeObjectsStack(pos, (EWallSide)side);
if (isEmpty == false)
{
CObjectStack* stack = map.addObjectsStack(pos, (EWallSide)side);
CObject object;
object.setType(3);
stack->addObject(object);
}
}
}
}
}
We simply parse all the "torch walls" in the map and create a stack if their "isEmpty" flag is false.
else if (wall->getType() == eWallTorch)
{
//------------------------------------------------------------------------------
// torch holder
CObjectStack* stack = map.findObjectsStack(mapPos, playerSide);
CRect rect = walls.drawOrnate(image, tablePos, side, (stack != NULL ? ORNATE_TORCH_FULL : ORNATE_TORCH_EMPTY));
if (tablePos == CVec2(2, 3) && side == eWallSideUp)
mouse.addArea(eMouseAreaG_WallTorch, rect, eCursor_Hand, (void*)mapPos.x, (void*)mapPos.y, (void*)playerSide);
}
Note that I modified drawOrnate so that it returns the rectangle of the ornate it draws. This way we don't have to recompute
else if (clickedArea->type == eMouseAreaG_WallTorch)
{
CVec2 pos;
EWallSide side;
pos.x = (int)clickedArea->param1;
pos.y = (int)clickedArea->param2;
side = (EWallSide)((int)clickedArea->param3);
int type = mouse.mObjectInHand.getType();
CObjectStack* stack = map.findObjectsStack(pos, side);
if (type == 0 && stack != NULL)
{
mouse.mObjectInHand = stack->getObject(0);
map.removeObjectsStack(pos, side);
}
else if (type == 3 && stack == NULL)
{
stack = map.addObjectsStack(pos, side);
stack->addObject(mouse.mObjectInHand);
mouse.mObjectInHand.setType(0);
}
characters[interface.selectedChampion].updateLoad();
}
Last words