first version of the Land of Dice

This commit is contained in:
Zeno Rogue 2021-05-27 13:00:20 +02:00
parent b879682d82
commit 6a6ed4ea0d
11 changed files with 330 additions and 98 deletions

View File

@ -104,6 +104,8 @@ EX bool canAttack(cell *c1, eMonster m1, cell *c2, eMonster m2, flagtype flags)
if(m1 == moArrowTrap && arrow_stuns(m2)) return true;
if(m2 == moAnimatedDie && !(flags & (AF_MAGIC | AF_CRUSH))) return false;
if(among(m2, moAltDemon, moHexDemon, moPair, moCrusher, moNorthPole, moSouthPole, moMonk) && !(flags & (AF_EAT | AF_MAGIC | AF_BULL | AF_CRUSH)))
return false;

View File

@ -1601,8 +1601,19 @@ void celldrawer::draw_features() {
poly_outline = OUTLINE_DEFAULT;
}
else if(c->wall == waDie) {
dice::draw_die(c, V);
else if(among(c->wall, waRichDie, waBlandDie)) {
color_t col = darkena(winf[c->wall].color, 0, 0xFF);
ld footphase;
Vboat = V;
bool animated = applyAnimation(c, Vboat, footphase, LAYER_BOAT);
if(animated) {
transmatrix U = inverse_shift(V, Vboat);
U = rgpushxto0(tC0(U));
Vboat = V * U;
}
die_target = V;
dice::draw_die(c, Vboat, 1, col);
}
else if(c->wall == waExplosiveBarrel) {

View File

@ -904,73 +904,148 @@ EX }
EX namespace dice {
/* vector<vector<int>> sides = {
{1, 3, 5}, {0, 4, 2}, {3, 1, 7}, {2, 6, 0}, {5, 7, 1}, {4, 0, 6}, {7, 5, 3}, {6, 2, 4}
}; */
vector<vector<int>> sides = {
struct die_structure {
vector<vector<int>> sides;
vector<vector<int>> spins;
vector<int> hardness;
int faces;
int order;
die_structure(int ord, const vector<vector<int>>& v) {
sides = v;
spins = sides;
faces = isize(sides);
order = ord;
for(int i=0; i<faces; i++)
for(int j=0; j<isize(sides[i]); j++) {
int i1 = sides[i][j];
spins[i][j] = -1;
for(int k=0; k<isize(sides[i1]); k++)
if(sides[i1][k] == i)
spins[i][j] = k;
if(spins[i][j] == -1)
println(hlog, "asymmetric");
}
hardness.resize(faces, 99);
hardness.back() = 0;
for(int it=0; it<faces; it++)
for(int i=0; i<faces; i++)
for(int j: sides[i])
hardness[i] = min(hardness[i], hardness[j]+1);
}
};
die_structure d20(5, {
{13-1, 7-1, 19-1}, {20-1, 12-1, 18-1}, {19-1, 17-1, 16-1}, {14-1, 18-1, 11-1}, {13-1, 18-1, 15-1},
{14-1, 9-1, 16-1}, { 1-1, 15-1, 17-1}, {20-1, 16-1, 10-1}, {19-1, 6-1, 11-1}, { 8-1, 17-1, 12-1},
{13-1, 9-1, 4-1}, { 2-1, 10-1, 15-1}, { 1-1, 11-1, 5-1}, {20-1, 4-1, 6-1}, { 7-1, 5-1, 12-1},
{ 8-1, 6-1, 3-1}, { 7-1, 10-1, 3-1}, { 2-1, 5-1, 4-1}, { 1-1, 3-1, 9-1}, { 8-1, 2-1, 14-1}
});
die_structure d8(4, {
{1, 3, 5}, {0, 4, 2}, {3, 1, 7}, {2, 6, 0}, {5, 7, 1}, {4, 0, 6}, {7, 5, 3}, {6, 2, 4}
});
die_structure d4(3, {{3,2,1}, {3,0,2}, {1,0,3}, {0,1,2}});
#if HDR
struct die_data {
struct die_structure *which;
int val;
int dir;
int happy();
};
#endif
vector<vector<int>> spins;
int order;
int faces() { return isize(sides); }
EX void generate_on(cell *c) {
c->wparam = hrand(faces()) + faces() * (1 + hrand(3) * 2);
int die_data::happy() {
if(val == which->faces-1) return 1;
if(val == 0) return -1;
return 0;
}
bool prepared;
void prepare() {
if(prepared) return;
prepared = true;
spins = sides;
order = faces() == 8 ? 4 : 5;
for(int i=0; i<faces(); i++)
for(int j=0; j<isize(sides[i]); j++) {
int i1 = sides[i][j];
spins[i][j] = -1;
for(int k=0; k<isize(sides[i1]); k++)
if(sides[i1][k] == i)
spins[i][j] = k;
if(spins[i][j] == -1)
println(hlog, "asymmetric");
}
EX map<cell*, die_data> data;
EX void generate_random(cell *c) {
auto& dd = data[c];
dd.which = pick(&d4, &d8, &d20);
dd.val = hrand(dd.which->faces);
dd.dir = 1 + hrand(3) * 2;
}
EX void generate_specific(cell *c, die_structure *ds, int min_hardness, int max_hardness) {
auto& dd = data[c];
dd.which = ds;
dd.dir = 1 + hrand(3) * 2;
vector<int> sides;
for(int i=0; i<ds->faces; i++)
if(ds->hardness[i] >= min_hardness && ds->hardness[i] <= max_hardness)
sides.push_back(i);
dd.val = sides[hrand(isize(sides))];
}
EX void roll(movei mi) {
prepare();
EX void generate_full(cell *c, int hard) {
int pct = hrand(100);
int pct2 = hrand(4000);
if(pct < 1) {
c->wall = waBlandDie;
generate_specific(c, &d4, 0, 99);
}
else if(pct < 3) {
c->wall = waBlandDie;
generate_specific(c, &d8, 0, 1);
}
else if(pct < 5) {
c->wall = waBlandDie;
generate_specific(c, &d20, 0, 1);
}
else if(pct < 9) {
c->wall = waRichDie;
generate_specific(c, &d20, 4, 5);
}
else if(pct < 10) {
c->wall = waRichDie;
generate_specific(c, &d8, 2, 3);
}
else if(pct2 < 1) {
c->monst = moAnimatedDie;
generate_specific(c, &d4, 0, 99);
}
else if(pct2 < 40) {
c->monst = moAnimatedDie;
generate_specific(c, &d8, 0, 99);
}
else if(pct2 < 40 + hard) {
c->monst = moAnimatedDie;
generate_specific(c, &d20, 0, 99);
}
}
EX die_data roll_effect(movei mi, die_data dd) {
auto &cto = mi.t;
auto &th = mi.s;
int rdir = mi.dir_force();
int t = th->type;
int val = th->wparam % faces();
int dir = th->wparam / faces();
int val = dd.val;
int dir = dd.dir;
int si = isize(sides[val]);
auto& dw = dd.which;
if(t % si) { println(hlog, "error: bad roll\n"); return; }
int si = isize(dw->sides[val]);
if(t % si) { println(hlog, "error: bad roll\n"); return dd; }
int sideid = (rdir - dir) * si / t;
if(sideid < 0) sideid += si;
int val1 = sides[val][sideid];
int val1 = dw->sides[val][sideid];
int si1 = isize(sides[val1]);
int si1 = isize(dw->sides[val1]);
println(hlog, tie(val, si, rdir), " to ", tie(val1, si1));
int sideid1 = spins[val][sideid];
int sideid1 = dw->spins[val][sideid];
int t1 = cto->type;
if(t1 % si1) { println(hlog, "error: bad roll target\n"); return; }
if(t1 % si1) { println(hlog, "error: bad roll target\n"); return dd; }
int rdir1 = mi.rev_dir_force();
@ -978,30 +1053,70 @@ EX namespace dice {
dir1 = gmod(dir1, t1);
th->wall = waNone;
cto->wall = waDie;
cto->wparam = val1 + faces() * dir1;
dd.val = val1;
dd.dir = dir1;
return dd;
}
EX void roll(movei mi) {
auto &cto = mi.t;
auto &th = mi.s;
changes.map_value(data, cto);
changes.map_value(data, th);
data[cto] = roll_effect(mi, data[th]);
data.erase(th);
}
EX void draw_die(cell *c, const shiftmatrix& V) {
prepare();
int val = c->wparam % faces();
int dir = c->wparam / faces();
queuestr(V, .5, its(val+1), 0xFFFFFFFF);
auto& side = sides[val];
int si = isize(side);
EX void draw_die(cell *c, const shiftmatrix& V, ld scale, color_t color) {
if(!data.count(c)) {
queuepoly(V, cgi.shAsymmetric, 0xFF0000FF);
return;
}
eGeometry orig = geometry;
bool fpp = GDIM == 3;
auto& dd = data[c];
int val = dd.val;
int dir = dd.dir;
auto& dw = dd.which;
for(int i=0; i<si; i++) {
int d = dir + c->type * i / isize(side);
d = gmod(d, c->type);
hyperpoint nxt = tC0(currentmap->adj(c, d));
hyperpoint mid = normalize(C0 * 1.3 + nxt * -.3);
queuestr(V * rgpushxto0(mid), .25, its(side[i]+1), 0xFFFFFFFF);
auto& side = dw->sides[val];
int si = isize(side);
if(c == lmouseover_distant) {
set<cell*> visited;
struct celldata_t {
cell* c;
die_data dd;
shiftmatrix V;
};
vector<celldata_t> data;
auto visit = [&] (cell *c, die_data dd, shiftmatrix V) {
if(visited.count(c)) return;
visited.insert(c);
data.emplace_back(celldata_t{c, dd, V});
};
visit(c, dd, V);
for(int i=0; i<isize(data); i++) {
auto dat = data[i];
queuestr(fpp ? dat.V * zpush(cgi.FLOOR) : dat.V, .5, its(dat.dd.val+1), 0xFF8000FF);
if(i <= 22)
forCellIdEx(c2, id, dat.c) if(!ctof(c2) && !visited.count(c2)) {
auto re = roll_effect(movei(dat.c, id), dat.dd);
shiftmatrix V2 = dat.V * currentmap->adj(dat.c, id);
gridline(dat.V, C0, V2, C0, 0xFF800080, 0);
visit(c2, re, V2);
}
}
}
shiftmatrix V1 = V * iddspin(c, dir) * spin(M_PI);
shiftmatrix V1 = V * ddspin(c, dir) * spin(M_PI);
vector<bool> face_drawn(faces(), false);
// loop:
vector<bool> face_drawn(dd.which->faces, false);
vector<pair<transmatrix, int> > facequeue;
@ -1011,46 +1126,64 @@ EX namespace dice {
facequeue.emplace_back(T, d);
};
add_to_queue(Id, val);
transmatrix S = Id;
ld outradius, inradius;
if(1) {
dynamicval<eGeometry> g(geometry, gSphere);
ld alpha = 360 * degree / order;
ld alpha = 360 * degree / dw->order;
inradius = edge_of_triangle_with_angles(alpha, 60*degree, 60*degree);
outradius = edge_of_triangle_with_angles(60*degree, alpha, 60*degree);
}
ld dieradius = 0.5;
hyperpoint shift = inverse_shift(V1, tC0(die_target));
hyperpoint log_shift = inverse_exp(shiftless(shift)) * (inradius / cgi.hexhexdist);
if(1) {
dynamicval<eGeometry> g(geometry, gSphere);
hyperpoint de = direct_exp(log_shift);
S = rgpushxto0(de);
if(GDIM == 3) {
for(int i=0; i<4; i++) swap(S[i][2], S[i][3]);
for(int i=0; i<4; i++) swap(S[2][i], S[3][i]);
}
for(int i=0; i<4; i++) S[i][1] *= -1;
}
add_to_queue(S, val);
ld dieradius = scale / 2;
if(dw->faces == 20) dieradius /= 1.3;
if(dw->faces == 8) dieradius /= 1.15;
ld base_to_base;
if(1) {
dynamicval<eGeometry> g(geometry, gSpace534);
hyperpoint h = cspin(2, 0, outradius) * zpush0(-dieradius);
hyperpoint h = cspin(2, 0, M_PI-outradius) * zpush0(-dieradius);
base_to_base = binsearch(-5, 5, [h] (ld d) {
return (zpush(d) * h)[2] >= sin_auto(vid.depth);
});
// ld base_to_base = cspin(2, 0, outradius) * zpush0(-dieradius);
}
vector<pair<ld, int> > ordering;
for(int i=0; i<faces(); i++) {
for(int i=0; i<dw->faces; i++) {
transmatrix T = facequeue[i].first;
int ws = facequeue[i].second;
for(int d=0; d<si; d++) {
dynamicval<eGeometry> g(geometry, gSpace534);
add_to_queue(T * cspin(0, 1, 2*M_PI*d/si) * cspin(2, 0, inradius) * cspin(0, 1, M_PI-2*M_PI*spins[ws][d]/si), sides[ws][d]);
add_to_queue(T * cspin(0, 1, 2*M_PI*d/si) * cspin(2, 0, inradius) * cspin(0, 1, M_PI-2*M_PI*dw->spins[ws][d]/si), dw->sides[ws][d]);
}
if(1) {
dynamicval<eGeometry> g(geometry, gSpace534);
hyperpoint h = zpush(base_to_base) * T * zpush0(dieradius);
ld z = asin_auto(h[2]);
ld z = fpp ? hdist0(h) : asin_auto(h[2]);
ordering.emplace_back(-z, i);
}
}
@ -1061,22 +1194,49 @@ EX namespace dice {
int i = o.second;
transmatrix T = facequeue[i].first;
transmatrix face;
hyperpoint sum = zpush(base_to_base) * C0 * -3;
auto sphere_to_space = [&] (hyperpoint h) {
if(fpp) return h;
ld z = asin_auto(h[2]);
h /= cos_auto(z);
h[2] = h[3]; h[3] = 0;
dynamicval<eGeometry> g(geometry, orig);
return zshift(h, geom3::scale_at_lev(z));
};
for(int d=0; d<=si; d++) {
hyperpoint h;
ld z = 0;
hyperpoint h, hs;
if(1) {
dynamicval<eGeometry> g(geometry, gSpace534);
h = zpush(base_to_base) * T * cspin(0, 1, 2*M_PI*(d+.5)/si) * cspin(2, 0, outradius) * zpush0(dieradius);
z = asin_auto(h[2]);
h /= cos_auto(z);
set_column(face, d, h);
sum += h;
hs = sphere_to_space(h);
}
h[2] = h[3]; h[3] = 0;
h = zshift(h, geom3::scale_at_lev(z));
curvepoint(h);
curvepoint(hs);
}
queuecurve(V1, 0xFFFFFFFF, 0x40A040FF, PPR::WALL);
}
set_column(face, 3, sum);
queuecurve(V1, 0xFFFFFFFF, color & 0xFFFFFF9F, PPR::WALL);
pointfunction pf = [&] (ld x, ld y) {
hyperpoint hs;
dynamicval<eGeometry> g(geometry, gSpace534);
return sphere_to_space(normalize(face * hyperpoint(1/3. -y/2 -x, 1/3. + y, 1/3. -y/2 +x, 1e-2)));
};
int fid = dw->faces - facequeue[i].second;
if(dw->faces == 4) fid = facequeue[i].second + 1;
string s;
if(fid == 6) s = "6.";
else if(fid == 9) s = "9.";
else s = its(fid);
write_in_space(V1, max_glfont_size, dw->faces < 10 ? -1.2 : -.75, s, 0xFFFFFFFF, 0, 8, PPR::WALL, pf);
}
}
EX }

View File

@ -1700,11 +1700,20 @@ LAND(0xC0C0FF, "Land of Dice", laDice, 0, itCursed, RESERVED,
ITEM('/', 0xD0D0D8, "Crystal Die", itDice, IC_TREASURE, ZERO, RESERVED, osNone,
"A nice souvenir from the Land of Dice. Make sure to collect the whole set!")
WALL('d', 0x181818, "Rollable Die", waDie, WF_WALL | WF_PUSHABLE, RESERVED, 0, sgNone, NODESC)
WALL('d', 0x181818, "Rollable Die", waDie1, ZERO, RESERVED, 0, sgNone, NODESC)
WALL('d', 0x181818, "Rollable Die", waDie2, ZERO, RESERVED, 0, sgNone, NODESC)
WALL('d', 0x7F6A10, "Unhappy Die", waRichDie, WF_WALL | WF_PUSHABLE, RESERVED, 0, sgNone,
"Sentent dice like to be in a position such that their highest number is on top, or somewhere close. "
"Unfortunately, someone has rolled this one into a wrong position, and did not fix this. "
"It will reward you if you roll it so that the highest number is on top again!")
WALL('d', 0x106010, "Happy Die", waBlandDie, WF_WALL | WF_PUSHABLE, RESERVED, 0, sgNone,
"A happy sentent die. You can roll it.")
MONSTER('d', 0x603010, "Animated Die", moAnimatedDie, ZERO, RESERVED, moHexDemon,
"When sentient dice are too long in an incorrect position, they start to move on their own, "
"and attack everyone. You can still convince Animated Dice of your good intentions by "
"rolling them into a position such that the highest number is on top. "
"If you do, the die will stop moving and (if it happens in the Land of Dice) you will be rewarded. "
"Other rolls and attacks are not allowed."
)
//shmupspecials
MONSTER( '@', 0xC0C0C0, "Rogue", moPlayer, CF_FACE_UP | CF_PLAYER, RESERVED, moNone, "In the Shoot'em Up mode, you are armed with thrown Knives.")

View File

@ -481,6 +481,7 @@ EX void bfs() {
else if(isMagneticPole(c2->monst)) havewhat |= HF_MAGNET;
else if(c2->monst == moAltDemon) havewhat |= HF_ALT;
else if(c2->monst == moHexDemon) havewhat |= HF_HEXD;
else if(c2->monst == moAnimatedDie) havewhat |= HF_HEXD;
else if(c2->monst == moMonk) havewhat |= HF_MONK;
else if(c2->monst == moShark || c2->monst == moCShark || among(c2->monst, moRusalka, moPike)) havewhat |= HF_SHARK;
else if(c2->monst == moAirElemental)

View File

@ -379,8 +379,22 @@ EX void pushThumper(const movei& mi) {
cto->wall = waCrateOnTarget;
th->wall = waCrateTarget;
}
else if(w == waDie)
dice::roll(mi);
else if(among(w, waRichDie, waBlandDie)) {
th->wall = waNone;
cto->wall = w;
animateMovement(mi, LAYER_BOAT);
dice::roll(mi);
if(w == waRichDie && dice::data[cto].happy() > 0) {
cto->wall = waBlandDie;
if(cto->land == laDice && th->land == laDice) {
items[itDice]++;
addMessage(XLAT("The die is now happy, and you are rewarded!"));
}
else {
addMessage(XLAT("The die is now happy, but won't reward you outside of the Land of Dice!"));
}
}
}
else
cto->wall = w;
if(explode) cto->wall = waFireTrap, cto->wparam = explode;
@ -389,7 +403,7 @@ EX void pushThumper(const movei& mi) {
}
EX bool canPushThumperOn(cell *tgt, cell *thumper, cell *player) {
if(thumper->wall == waDie && tgt->type == 7)
if(among(thumper->wall, waRichDie, waBlandDie) && ctof(tgt))
return false;
if(tgt->wall == waBoat || tgt->wall == waStrandedBoat) return false;
if(isReptile(tgt->wall)) return false;

View File

@ -814,7 +814,7 @@ EX bool drawItemType(eItem it, cell *c, const shiftmatrix& V, color_t icol, int
(it == itBombEgg || it == itTrollEgg) ? &cgi.shEgg :
it == itFrog ? &cgi.shDisk :
it == itHunting ? &cgi.shTriangle :
it == itDodeca ? &cgi.shDodeca :
(it == itDodeca || it == itDice) ? &cgi.shDodeca :
xch == '*' ? &cgi.shGem[ct6] :
xch == '(' ? &cgi.shKnife :
it == itShard ? &cgi.shMFloor.b[0] :
@ -2251,7 +2251,15 @@ EX bool drawMonsterType(eMonster m, cell *where, const shiftmatrix& V1, color_t
queuepoly(V, cgi.shAsteroid[1], darkena(col, 0, 0xFF));
return true;
}
case moAnimatedDie: {
if(where)
dice::draw_die(where, V, 1, darkena(col, 0, 0xFF));
else
queuepoly(V, cgi.shDodeca, darkena(col, 0, 0xFF));
return true;
}
default: ;
}
@ -2518,6 +2526,8 @@ void drawWormSegments() {
EX bool dont_face_pc = false;
EX shiftmatrix die_target;
EX bool drawMonster(const shiftmatrix& Vparam, int ct, cell *c, color_t col, color_t asciicol) {
#if CAP_SHAPES
@ -2876,6 +2886,13 @@ EX bool drawMonster(const shiftmatrix& Vparam, int ct, cell *c, color_t col, col
// golems, knights, and hyperbugs don't face the player (mondir-controlled)
// also whatever in the lineview mode, and whatever in the quotient geometry
else if(c->monst == moAnimatedDie) {
transmatrix U = inverse_shift(Vparam, Vs);
U = rgpushxto0(tC0(U));
die_target = Vparam;
res = res && drawMonsterTypeDH(m, c, Vparam * U, col, darkhistory, footphase, asciicol);
}
else if((hasFacing(c) && c->mondir != NODIR) || history::on || quotient || dont_face_pc) {
if(c->monst == moKrakenH) Vs = Vb, nospins = nospinb;
if(!nospins && c->mondir < c->type) Vs = Vs * ddspin(c, c->mondir, M_PI);

View File

@ -2586,16 +2586,8 @@ EX void giantLandSwitch(cell *c, int d, cell *from) {
}
case laDice: {
if(fargen) {
int pct = hrand(100);
if(pct < 10)
c->wall = waDie;
// c->wall = pick(waNone, waNone, waStone, waNone, waNone, waStone, waNone, waNone, waStone, waBigStatue, waCrateCrate, waDie);
if(c->wall == waDie) {
if(ctof(c)) c->wall = waNone;
else dice::generate_on(c);
}
}
if(fargen && !ctof(c))
dice::generate_full(c, items[itDice] + yendor::hardness());
break;
}

View File

@ -102,6 +102,9 @@ EX void moveEffect(const movei& mi, eMonster m) {
animateMovement(mi.rev(), LAYER_BOAT);
tortoise::move_baby(cf, ct);
}
if(m == moAnimatedDie && mi.proper())
dice::roll(mi);
}
EX void moveMonster(const movei& mi) {
@ -1086,6 +1089,11 @@ EX void groupmove2(const movei& mi, eMonster movtype, flagtype mf) {
}
}
else return;
if(c->monst == moAnimatedDie) {
forCellEx(c3, from) if(c3 != c && c3->monst == moAnimatedDie)
return;
}
if(from->monst) {
if(mf & MF_MOUNT) {

View File

@ -808,6 +808,8 @@ EX eMonster summonedAt(cell *dest) {
return moMiner;
if(dest->wall == waMineOpen || dest->wall == waMineMine || dest->wall == waMineUnknown)
return moBomberbird;
if(dest->wall == waRichDie)
return moAnimatedDie;
if(dest->wall == waTrapdoor)
return dest->land == laPalace ? moFatGuard : moOrangeDog;
if(dest->land == laFrog && dest->wall == waNone) {
@ -906,6 +908,8 @@ void summonAt(cell *dest) {
dest->stuntime = 3;
if(dest->monst == moPirate || dest->monst == moViking || (dest->monst == moRatling && dest->wall == waSea))
dest->wall = waBoat, dest->item = itNone;
if(dest->monst == moAnimatedDie)
dest->wall = waNone;
if(dest->monst == moViking && dest->land == laKraken)
dest->item = itOrbFish;
if(dest->wall == waStrandedBoat)

View File

@ -628,6 +628,18 @@ bool pcmove::actual_move() {
activateActiv(c2, true);
return after_instant(false);
}
if(c2->monst == moAnimatedDie) {
mip = determinePush(cwt, subdir, [c2] (cell *c) { return canPushThumperOn(c, c2, cwt.at); });
if(mip.proper()) {
auto tgt = roll_effect(mip, dice::data[c2]);
if(tgt.happy() > 0) {
changes.ccell(c2);
c2->monst = moNone;
c2->wall = waRichDie;
}
}
}
if(isPushable(c2->wall) && !c2->monst && !nonAdjacentPlayer(c2, cwt.at) && fmsMove) {
mip = determinePush(cwt, subdir, [c2] (cell *c) { return canPushThumperOn(c, c2, cwt.at); });
@ -745,6 +757,8 @@ void pcmove::tell_why_cannot_attack() {
addMessage(XLAT("You cannot attack Raiders directly!"));
else if(isSwitch(c2->monst))
addMessage(XLAT("You cannot attack Jellies in their wall form!"));
else if(c2->monst == moAnimatedDie)
addMessage(XLAT("You can only push this die if the highest number would be on the top!"));
else
addMessage(XLAT("For some reason... cannot attack!"));
}