File: HandleEffectM.hs

package info (click to toggle)
haskell-lambdahack 0.11.0.1-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 4,056 kB
  • sloc: haskell: 45,636; makefile: 219
file content (2296 lines) | stat: -rw-r--r-- 107,894 bytes parent folder | download | duplicates (3)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
1632
1633
1634
1635
1636
1637
1638
1639
1640
1641
1642
1643
1644
1645
1646
1647
1648
1649
1650
1651
1652
1653
1654
1655
1656
1657
1658
1659
1660
1661
1662
1663
1664
1665
1666
1667
1668
1669
1670
1671
1672
1673
1674
1675
1676
1677
1678
1679
1680
1681
1682
1683
1684
1685
1686
1687
1688
1689
1690
1691
1692
1693
1694
1695
1696
1697
1698
1699
1700
1701
1702
1703
1704
1705
1706
1707
1708
1709
1710
1711
1712
1713
1714
1715
1716
1717
1718
1719
1720
1721
1722
1723
1724
1725
1726
1727
1728
1729
1730
1731
1732
1733
1734
1735
1736
1737
1738
1739
1740
1741
1742
1743
1744
1745
1746
1747
1748
1749
1750
1751
1752
1753
1754
1755
1756
1757
1758
1759
1760
1761
1762
1763
1764
1765
1766
1767
1768
1769
1770
1771
1772
1773
1774
1775
1776
1777
1778
1779
1780
1781
1782
1783
1784
1785
1786
1787
1788
1789
1790
1791
1792
1793
1794
1795
1796
1797
1798
1799
1800
1801
1802
1803
1804
1805
1806
1807
1808
1809
1810
1811
1812
1813
1814
1815
1816
1817
1818
1819
1820
1821
1822
1823
1824
1825
1826
1827
1828
1829
1830
1831
1832
1833
1834
1835
1836
1837
1838
1839
1840
1841
1842
1843
1844
1845
1846
1847
1848
1849
1850
1851
1852
1853
1854
1855
1856
1857
1858
1859
1860
1861
1862
1863
1864
1865
1866
1867
1868
1869
1870
1871
1872
1873
1874
1875
1876
1877
1878
1879
1880
1881
1882
1883
1884
1885
1886
1887
1888
1889
1890
1891
1892
1893
1894
1895
1896
1897
1898
1899
1900
1901
1902
1903
1904
1905
1906
1907
1908
1909
1910
1911
1912
1913
1914
1915
1916
1917
1918
1919
1920
1921
1922
1923
1924
1925
1926
1927
1928
1929
1930
1931
1932
1933
1934
1935
1936
1937
1938
1939
1940
1941
1942
1943
1944
1945
1946
1947
1948
1949
1950
1951
1952
1953
1954
1955
1956
1957
1958
1959
1960
1961
1962
1963
1964
1965
1966
1967
1968
1969
1970
1971
1972
1973
1974
1975
1976
1977
1978
1979
1980
1981
1982
1983
1984
1985
1986
1987
1988
1989
1990
1991
1992
1993
1994
1995
1996
1997
1998
1999
2000
2001
2002
2003
2004
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
2027
2028
2029
2030
2031
2032
2033
2034
2035
2036
2037
2038
2039
2040
2041
2042
2043
2044
2045
2046
2047
2048
2049
2050
2051
2052
2053
2054
2055
2056
2057
2058
2059
2060
2061
2062
2063
2064
2065
2066
2067
2068
2069
2070
2071
2072
2073
2074
2075
2076
2077
2078
2079
2080
2081
2082
2083
2084
2085
2086
2087
2088
2089
2090
2091
2092
2093
2094
2095
2096
2097
2098
2099
2100
2101
2102
2103
2104
2105
2106
2107
2108
2109
2110
2111
2112
2113
2114
2115
2116
2117
2118
2119
2120
2121
2122
2123
2124
2125
2126
2127
2128
2129
2130
2131
2132
2133
2134
2135
2136
2137
2138
2139
2140
2141
2142
2143
2144
2145
2146
2147
2148
2149
2150
2151
2152
2153
2154
2155
2156
2157
2158
2159
2160
2161
2162
2163
2164
2165
2166
2167
2168
2169
2170
2171
2172
2173
2174
2175
2176
2177
2178
2179
2180
2181
2182
2183
2184
2185
2186
2187
2188
2189
2190
2191
2192
2193
2194
2195
2196
2197
2198
2199
2200
2201
2202
2203
2204
2205
2206
2207
2208
2209
2210
2211
2212
2213
2214
2215
2216
2217
2218
2219
2220
2221
2222
2223
2224
2225
2226
2227
2228
2229
2230
2231
2232
2233
2234
2235
2236
2237
2238
2239
2240
2241
2242
2243
2244
2245
2246
2247
2248
2249
2250
2251
2252
2253
2254
2255
2256
2257
2258
2259
2260
2261
2262
2263
2264
2265
2266
2267
2268
2269
2270
2271
2272
2273
2274
2275
2276
2277
2278
2279
2280
2281
2282
2283
2284
2285
2286
2287
2288
2289
2290
2291
2292
2293
2294
2295
2296
{-# LANGUAGE TupleSections #-}
-- | Handle effects. They are most often caused by requests sent by clients
-- but sometimes also caused by projectiles or periodically activated items.
module Game.LambdaHack.Server.HandleEffectM
  ( UseResult(..), EffToUse(..), EffApplyFlags(..)
  , applyItem, cutCalm, kineticEffectAndDestroy, effectAndDestroyAndAddKill
  , itemEffectEmbedded, highestImpression, dominateFidSfx
  , dropAllEquippedItems, pickDroppable, consumeItems, dropCStoreItem
#ifdef EXPOSE_INTERNAL
    -- * Internal operations
  , applyKineticDamage, refillHP, effectAndDestroy, imperishableKit
  , itemEffectDisco, effectSem
  , effectBurn, effectExplode, effectRefillHP, effectRefillCalm
  , effectDominate, dominateFid, effectImpress, effectPutToSleep, effectYell
  , effectSummon, effectAscend, findStairExit, switchLevels1, switchLevels2
  , effectEscape, effectParalyze, paralyze, effectParalyzeInWater
  , effectInsertMove, effectTeleport, effectCreateItem
  , effectDestroyItem, effectDropItem, effectConsumeItems
  , effectRecharge, effectPolyItem, effectRerollItem, effectDupItem
  , effectIdentify, identifyIid, effectDetect, effectDetectX, effectSendFlying
  , sendFlyingVector, effectApplyPerfume, effectAtMostOneOf, effectOneOf
  , effectAndEffect, effectAndEffectSem, effectOrEffect, effectSeqEffect
  , effectWhen, effectUnless, effectIfThenElse
  , effectVerbNoLonger, effectVerbMsg, effectVerbMsgFail
#endif
  ) where

import Prelude ()

import Game.LambdaHack.Core.Prelude

import           Data.Bits (xor)
import qualified Data.EnumMap.Strict as EM
import qualified Data.EnumSet as ES
import qualified Data.HashMap.Strict as HM
import           Data.Int (Int64)
import           Data.Key (mapWithKeyM_)
import qualified Data.Text as T

import           Game.LambdaHack.Atomic
import           Game.LambdaHack.Common.Actor
import           Game.LambdaHack.Common.ActorState
import           Game.LambdaHack.Common.Analytics
import           Game.LambdaHack.Common.Faction
import           Game.LambdaHack.Common.Item
import qualified Game.LambdaHack.Common.ItemAspect as IA
import           Game.LambdaHack.Common.Kind
import           Game.LambdaHack.Common.Level
import           Game.LambdaHack.Common.Misc
import           Game.LambdaHack.Common.MonadStateRead
import           Game.LambdaHack.Common.Perception
import           Game.LambdaHack.Common.Point
import           Game.LambdaHack.Common.ReqFailure
import           Game.LambdaHack.Common.State
import qualified Game.LambdaHack.Common.Tile as Tile
import           Game.LambdaHack.Common.Time
import           Game.LambdaHack.Common.Types
import           Game.LambdaHack.Common.Vector
import           Game.LambdaHack.Content.FactionKind
import           Game.LambdaHack.Content.ItemKind (ItemKind)
import qualified Game.LambdaHack.Content.ItemKind as IK
import           Game.LambdaHack.Content.RuleKind
import qualified Game.LambdaHack.Core.Dice as Dice
import           Game.LambdaHack.Core.Random
import           Game.LambdaHack.Definition.Ability (ActivationFlag (..))
import qualified Game.LambdaHack.Definition.Ability as Ability
import           Game.LambdaHack.Definition.Defs
import           Game.LambdaHack.Server.CommonM
import           Game.LambdaHack.Server.ItemM
import           Game.LambdaHack.Server.ItemRev
import           Game.LambdaHack.Server.MonadServer
import           Game.LambdaHack.Server.PeriodicM
import           Game.LambdaHack.Server.ServerOptions
import           Game.LambdaHack.Server.State

-- * Semantics of effects

data UseResult = UseDud | UseId | UseUp
  deriving (Eq, Ord)

data EffToUse = EffBare | EffBareAndOnCombine | EffOnCombine
  deriving Eq

data EffApplyFlags = EffApplyFlags
  { effToUse            :: EffToUse
  , effVoluntary        :: Bool
  , effUseAllCopies     :: Bool
  , effKineticPerformed :: Bool
  , effActivation       :: Ability.ActivationFlag
  , effMayDestroy       :: Bool
  }

applyItem :: MonadServerAtomic m => ActorId -> ItemId -> CStore -> m ()
applyItem aid iid cstore = do
  execSfxAtomic $ SfxApply aid iid
  let c = CActor aid cstore
  -- Treated as if the actor hit himself with the item as a weapon,
  -- incurring both the kinetic damage and effect, hence the same call
  -- as in @reqMelee@.
  let effApplyFlags = EffApplyFlags
        { effToUse            = EffBareAndOnCombine
        , effVoluntary        = True
        , effUseAllCopies     = False
        , effKineticPerformed = False
        , effActivation       = ActivationTrigger
        , effMayDestroy       = True
        }
  void $ kineticEffectAndDestroy effApplyFlags aid aid aid iid c

applyKineticDamage :: MonadServerAtomic m
                   => ActorId -> ActorId -> ItemId -> m Bool
applyKineticDamage source target iid = do
  itemKind <- getsState $ getIidKindServer iid
  if IK.idamage itemKind == 0 then return False else do  -- speedup
    sb <- getsState $ getActorBody source
    hurtMult <- getsState $ armorHurtBonus source target
    totalDepth <- getsState stotalDepth
    Level{ldepth} <- getLevel (blid sb)
    dmg <- rndToAction $ castDice ldepth totalDepth $ IK.idamage itemKind
    let rawDeltaHP = into @Int64 hurtMult * xM dmg `divUp` 100
        speedDeltaHP = case btrajectory sb of
          Just (_, speed) | bproj sb -> - modifyDamageBySpeed rawDeltaHP speed
          _ -> - rawDeltaHP
    if speedDeltaHP < 0 then do  -- damage the target, never heal
      refillHP source target speedDeltaHP
      return True
    else return False

refillHP :: MonadServerAtomic m => ActorId -> ActorId -> Int64 -> m ()
refillHP source target speedDeltaHP = assert (speedDeltaHP /= 0) $ do
  tbOld <- getsState $ getActorBody target
  actorMaxSk <- getsState $ getActorMaxSkills target
  -- We don't ignore even tiny HP drains, because they can be very weak
  -- enemy projectiles and so will recur and in total can be deadly
  -- and also AI should rather be stupidly aggressive than stupidly lethargic.
  let serious = source /= target && not (bproj tbOld)
      hpMax = Ability.getSk Ability.SkMaxHP actorMaxSk
      deltaHP0 | serious && speedDeltaHP < minusM =
                 -- If overfull, at least cut back to max, unless minor drain.
                 min speedDeltaHP (xM hpMax - bhp tbOld)
               | otherwise = speedDeltaHP
      deltaHP = if | deltaHP0 > 0 && bhp tbOld > xM 999 ->  -- UI limit
                     tenthM  -- avoid nop, to avoid loops
                   | deltaHP0 < 0 && bhp tbOld < - xM 999 ->
                     -tenthM
                   | otherwise -> deltaHP0
  execUpdAtomic $ UpdRefillHP target deltaHP
  when serious $ cutCalm target
  tb <- getsState $ getActorBody target
  fact <- getsState $ (EM.! bfid tb) . sfactionD
  when (not (bproj tb) && fhasPointman (gkind fact)) $
    -- If leader just lost all HP, change the leader early (not when destroying
    -- the actor), to let players rescue him, especially if he's slowed
    -- by the attackers.
    when (bhp tb <= 0 && bhp tbOld > 0) $ do
      -- If all other party members dying, leadership will switch
      -- to one of them, which seems questionable, but it's rare
      -- and the disruption servers to underline the dire circumstance.
      electLeader (bfid tb) (blid tb) target
      mleader <- getsState $ gleader . (EM.! bfid tb) . sfactionD
      -- If really nobody else in the party, make him the leader back again
      -- on the oft chance that he gets revived by a projectile, etc.
      when (isNothing mleader) $
        execUpdAtomic $ UpdLeadFaction (bfid tb) Nothing $ Just target

cutCalm :: MonadServerAtomic m => ActorId -> m ()
cutCalm target = do
  tb <- getsState $ getActorBody target
  actorMaxSk <- getsState $ getActorMaxSkills target
  let upperBound = if hpTooLow tb actorMaxSk
                   then 2  -- to trigger domination on next attack, etc.
                   else xM $ Ability.getSk Ability.SkMaxCalm actorMaxSk
      deltaCalm = min minusM2 (upperBound - bcalm tb)
  -- HP loss decreases Calm by at least @minusM2@ to avoid "hears something",
  -- which is emitted when decreasing Calm by @minusM1@.
  updateCalm target deltaCalm

-- Here kinetic damage is applied. This is necessary so that the same
-- AI benefit calculation may be used for flinging and for applying items.
kineticEffectAndDestroy :: MonadServerAtomic m
                        => EffApplyFlags
                        -> ActorId -> ActorId -> ActorId -> ItemId -> Container
                        -> m UseResult
kineticEffectAndDestroy effApplyFlags0@EffApplyFlags{..}
                        killer source target iid c = do
  bag <- getsState $ getContainerBag c
  case iid `EM.lookup` bag of
    Nothing -> error $ "" `showFailure` (source, target, iid, c)
    Just kit -> do
      itemFull <- getsState $ itemToFull iid
      tbOld <- getsState $ getActorBody target
      localTime <- getsState $ getLocalTime (blid tbOld)
      let recharged = hasCharge localTime kit
      -- If neither kinetic hit nor any effect is activated, there's no chance
      -- the items can be destroyed or even timeout changes, so we abort early.
      if not recharged then return UseDud else do
        effKineticPerformed2 <- applyKineticDamage source target iid
        tb <- getsState $ getActorBody target
        -- Sometimes victim heals just after we registered it as killed,
        -- but that's OK, an actor killed two times is similar enough
        -- to two killed.
        when (effKineticPerformed2  -- speedup
              && bhp tb <= 0 && bhp tbOld > 0) $ do
          sb <- getsState $ getActorBody source
          arWeapon <- getsState $ (EM.! iid) . sdiscoAspect
          let killHow | not (bproj sb) =
                        if effVoluntary
                        then KillKineticMelee
                        else KillKineticPush
                      | IA.checkFlag Ability.Blast arWeapon = KillKineticBlast
                      | otherwise = KillKineticRanged
          addKillToAnalytics killer killHow (bfid tbOld) (btrunk tbOld)
        let effApplyFlags = effApplyFlags0
              { effUseAllCopies     = fst kit <= 1
              , effKineticPerformed = effKineticPerformed2
              }
        effectAndDestroyAndAddKill effApplyFlags
                                   killer source target iid c itemFull

effectAndDestroyAndAddKill :: MonadServerAtomic m
                           => EffApplyFlags
                           -> ActorId -> ActorId -> ActorId -> ItemId
                           -> Container -> ItemFull
                           -> m UseResult
effectAndDestroyAndAddKill effApplyFlags0@EffApplyFlags{..}
                           killer source target iid c itemFull = do
  tbOld <- getsState $ getActorBody target
  triggered <- effectAndDestroy effApplyFlags0 source target iid c itemFull
  tb <- getsState $ getActorBody target
  -- Sometimes victim heals just after we registered it as killed,
  -- but that's OK, an actor killed two times is similar enough to two killed.
  when (bhp tb <= 0 && bhp tbOld > 0) $ do
    sb <- getsState $ getActorBody source
    arWeapon <- getsState $ (EM.! iid) . sdiscoAspect
    let killHow | not (bproj sb) =
                  if effVoluntary then KillOtherMelee else KillOtherPush
                | IA.checkFlag Ability.Blast arWeapon = KillOtherBlast
                | otherwise = KillOtherRanged
    addKillToAnalytics killer killHow (bfid tbOld) (btrunk tbOld)
  return triggered

effectAndDestroy :: MonadServerAtomic m
                 => EffApplyFlags
                 -> ActorId -> ActorId -> ItemId -> Container -> ItemFull
                 -> m UseResult
effectAndDestroy effApplyFlags0@EffApplyFlags{..} source target iid container
                 itemFull@ItemFull{itemDisco, itemKindId, itemKind} = do
  bag <- getsState $ getContainerBag container
  let (itemK, itemTimers) = bag EM.! iid
      effs = case effToUse of
        EffBare -> if effActivation == ActivationOnSmash
                   then IK.strengthOnSmash itemKind
                   else IK.ieffects itemKind
        EffBareAndOnCombine ->
          IK.ieffects itemKind ++ IK.strengthOnCombine itemKind
        EffOnCombine -> IK.strengthOnCombine itemKind
      arItem = case itemDisco of
        ItemDiscoFull itemAspect -> itemAspect
        _ -> error "effectAndDestroy: server ignorant about an item"
      timeout = IA.aTimeout arItem
  lid <- getsState $ lidFromC container
  localTime <- getsState $ getLocalTime lid
  let it1 = filter (charging localTime) itemTimers
      len = length it1
      recharged = len < itemK
                  || effActivation `elem` [ActivationOnSmash, ActivationConsume]
  -- If the item has no charges and the special cases don't apply
  -- we speed up by shortcutting early, because we don't need to activate
  -- effects and we know kinetic hit was not performed (no charges to do so
  -- and in case of @OnSmash@ and @ActivationConsume@,
  -- only effects are triggered).
  if not recharged then return UseDud else do
    let timeoutTurns = timeDeltaScale (Delta timeTurn) timeout
        newItemTimer = createItemTimer localTime timeoutTurns
        it2 = if timeout > 0 && recharged
              then if effActivation == ActivationPeriodic
                      && IA.checkFlag Ability.Fragile arItem
                   then replicate (itemK - length it1) newItemTimer ++ it1
                           -- copies are spares only; one fires, all discharge
                   else take (itemK - length it1) [newItemTimer] ++ it1
                           -- copies all fire, turn by turn; <= 1 discharges
              else itemTimers
        kit2 = (1, take 1 it2)
        !_A = assert (len <= itemK `blame` (source, target, iid, container)) ()
    -- We use up the charge even if eventualy every effect fizzles. Tough luck.
    -- At least we don't destroy the item in such case.
    -- Also, we ID it regardless.
    unless (itemTimers == it2) $
      execUpdAtomic $ UpdTimeItem iid container itemTimers it2
    -- We have to destroy the item before the effect affects the item
    -- or affects the actor holding it or standing on it (later on we could
    -- lose track of the item and wouldn't be able to destroy it) .
    -- This is OK, because we don't remove the item type from various
    -- item dictionaries, just an individual copy from the container,
    -- so, e.g., the item can be identified after it's removed.
    let imperishable = not effMayDestroy
                       || imperishableKit effActivation itemFull
    unless imperishable $
      execUpdAtomic $ UpdLoseItem False iid kit2 container
    -- At this point, the item is potentially no longer in container
    -- @container@, therefore beware of assuming so in the code below.
    triggeredEffect <- itemEffectDisco effApplyFlags0 source target iid
                                       itemKindId itemKind container effs
    sb <- getsState $ getActorBody source
    let triggered = if effKineticPerformed then UseUp else triggeredEffect
        mEmbedPos = case container of
          CEmbed _ p -> Just p
          _ -> Nothing
    if | triggered == UseUp
         && mEmbedPos /= Just (bpos sb)  -- treading water, etc.
         && effActivation `notElem` [ActivationTrigger, ActivationMeleeable]
              -- do not repeat almost the same msg
         && (effActivation /= ActivationOnSmash  -- only tells condition ends
             && effActivation /= ActivationPeriodic
             || not (IA.checkFlag Ability.Condition arItem)) -> do
           -- Effects triggered; main feedback comes from them,
           -- but send info so that clients can log it.
           let verbose = effActivation == ActivationUnderRanged
                         || effActivation == ActivationUnderMelee
           execSfxAtomic $ SfxItemApplied verbose iid container
       | triggered /= UseUp
         && effActivation /= ActivationOnSmash
         && effActivation /= ActivationPeriodic
              -- periodic effects repeat and so spam
         && effActivation
            `notElem` [ActivationUnderRanged, ActivationUnderMelee]
              -- and so do effects under attack
         && not (bproj sb)  -- projectiles can be very numerous
         && isNothing mEmbedPos  ->  -- embeds may be just flavour
           -- Announce no effect, which is rare and wastes time, so noteworthy.
           execSfxAtomic $ SfxMsgFid (bfid sb) $
             if any IK.forApplyEffect effs
             then SfxFizzles iid container
                    -- something didn't work despite promising effects
             else SfxNothingHappens iid container  -- fully expected
       | otherwise -> return ()  -- all the spam cases
    -- If none of item's effects nor a kinetic hit were performed,
    -- we recreate the item (assuming we deleted the item above).
    -- Regardless, we don't rewind the time, because some info is gained
    -- (that the item does not exhibit any effects in the given context).
    unless (imperishable || triggered == UseUp) $
      execUpdAtomic $ UpdSpotItem False iid kit2 container
    return triggered

imperishableKit :: ActivationFlag -> ItemFull -> Bool
imperishableKit effActivation itemFull =
  let arItem = aspectRecordFull itemFull
  in IA.checkFlag Ability.Durable arItem
     || effActivation == ActivationPeriodic
        && not (IA.checkFlag Ability.Fragile arItem)

-- The item is triggered exactly once. If there are more copies,
-- they are left to be triggered next time.
-- If the embed no longer exists at the given position, effect fizzles.
itemEffectEmbedded :: MonadServerAtomic m
                   => EffToUse -> Bool -> ActorId -> LevelId -> Point -> ItemId
                   -> m UseResult
itemEffectEmbedded effToUse effVoluntary aid lid tpos iid = do
  embeds2 <- getsState $ getEmbedBag lid tpos
    -- might have changed due to other embedded items invocations
  if iid `EM.notMember` embeds2
  then return UseDud
  else do
    -- First embedded item may move actor to another level, so @lid@
    -- may be unequal to @blid sb@.
    let c = CEmbed lid tpos
    -- Treated as if the actor hit himself with the embedded item as a weapon,
    -- incurring both the kinetic damage and effect, hence the same call
    -- as in @reqMelee@. Information whether this happened due to being pushed
    -- is preserved, but how did the pushing is lost, so we blame the victim.
    let effApplyFlags = EffApplyFlags
          { effToUse
          , effVoluntary
          , effUseAllCopies     = False
          , effKineticPerformed = False
          , effActivation       = if effToUse == EffOnCombine
                                  then ActivationOnCombine
                                  else ActivationEmbed
          , effMayDestroy       = True
          }
    kineticEffectAndDestroy effApplyFlags aid aid aid iid c

-- | The source actor affects the target actor, with a given item.
-- If any of the effects fires up, the item gets identified.
-- Even using raw damage (beating the enemy with the magic wand,
-- for example) identifies the item. This means a costly @UpdDiscover@
-- is processed for each random timeout weapon hit and for most projectiles,
-- but at least not for most explosion particles nor plain organs.
-- And if not needed, the @UpdDiscover@ are eventually not sent to clients.
-- So, enemy missiles that hit us are no longer mysterious until picked up,
-- which is for the better, because the client knows their charging status
-- and so can generate accurate messages in the case when not recharged.
-- This also means that thrown consumables in flasks sturdy enough to cause
-- damage are always identified at hit, even if no effect activated.
-- So throwing them at foes is a better identification method than applying.
--
-- Note that if we activate a durable non-passive item, e.g., a spiked shield,
-- from the ground, it will get identified, which is perfectly fine,
-- until we want to add sticky armor that can't be easily taken off
-- (and, e.g., has some maluses).
itemEffectDisco :: MonadServerAtomic m
                => EffApplyFlags
                -> ActorId -> ActorId -> ItemId
                -> ContentId ItemKind -> ItemKind -> Container -> [IK.Effect]
                -> m UseResult
itemEffectDisco effApplyFlags0@EffApplyFlags{..}
                source target iid itemKindId itemKind c effs = do
  urs <- mapM (effectSem effApplyFlags0 source target iid c) effs
  let ur = case urs of
        [] -> UseDud  -- there was no effects
        _ -> maximum urs
  -- Note: @UseId@ suffices for identification, @UseUp@ is not necessary.
  when (ur >= UseId || effKineticPerformed) $
    identifyIid iid c itemKindId itemKind
  return ur

-- | Source actor affects target actor, with a given effect and it strength.
-- Both actors are on the current level and can be the same actor.
-- The item may or may not still be in the container.
effectSem :: MonadServerAtomic m
          => EffApplyFlags
          -> ActorId -> ActorId -> ItemId -> Container -> IK.Effect
          -> m UseResult
effectSem effApplyFlags0@EffApplyFlags{..}
          source target iid c effect = do
  let recursiveCall = effectSem effApplyFlags0 source target iid c
  sb <- getsState $ getActorBody source
  -- @execSfx@ usually comes last in effect semantics, but not always
  -- and we are likely to introduce more variety.
  let execSfx = execSfxAtomic $ SfxEffect (bfid sb) target iid effect 0
      execSfxSource = execSfxAtomic $ SfxEffect (bfid sb) source iid effect 0
  case effect of
    IK.Burn nDm -> effectBurn nDm source target iid
    IK.Explode t -> effectExplode execSfx t source target c
    IK.RefillHP p -> effectRefillHP p source target iid
    IK.RefillCalm p -> effectRefillCalm execSfx p source target
    IK.Dominate -> effectDominate source target iid
    IK.Impress -> effectImpress recursiveCall execSfx source target
    IK.PutToSleep -> effectPutToSleep execSfx target
    IK.Yell -> effectYell execSfx target
    IK.Summon grp nDm -> effectSummon grp nDm iid source target effActivation
    IK.Ascend p -> effectAscend recursiveCall execSfx p source target c
    IK.Escape{} -> effectEscape execSfx source target
    IK.Paralyze nDm -> effectParalyze execSfx nDm source target
    IK.ParalyzeInWater nDm -> effectParalyzeInWater execSfx nDm source target
    IK.InsertMove nDm -> effectInsertMove execSfx nDm source target
    IK.Teleport nDm -> effectTeleport execSfx nDm source target
    IK.CreateItem mcount store grp tim ->
      effectCreateItem (Just $ bfid sb) mcount source target (Just iid)
                       store grp tim
    IK.DestroyItem n k store grp ->
      effectDestroyItem execSfx n k store target grp
    IK.ConsumeItems tools raw -> effectConsumeItems execSfx iid target tools raw
    IK.DropItem n k store grp -> effectDropItem execSfx iid n k store grp target
    IK.Recharge n dice -> effectRecharge True execSfx iid n dice target
    IK.Discharge n dice -> effectRecharge False execSfx iid n dice target
    IK.PolyItem -> effectPolyItem execSfx iid target
    IK.RerollItem -> effectRerollItem execSfx iid target
    IK.DupItem -> effectDupItem execSfx iid target
    IK.Identify -> effectIdentify execSfx iid target
    IK.Detect d radius -> effectDetect execSfx d radius target c
    IK.SendFlying tmod ->
      effectSendFlying execSfx tmod source target c Nothing
    IK.PushActor tmod ->
      effectSendFlying execSfx tmod source target c (Just True)
    IK.PullActor tmod ->
      effectSendFlying execSfx tmod source target c (Just False)
    IK.ApplyPerfume -> effectApplyPerfume execSfx target
    IK.AtMostOneOf l -> effectAtMostOneOf recursiveCall l
    IK.OneOf l -> effectOneOf recursiveCall l
    IK.OnSmash _ -> return UseDud  -- ignored under normal circumstances
    IK.OnCombine _ -> return UseDud  -- ignored under normal circumstances
    IK.OnUser eff -> effectSem effApplyFlags0 source source iid c eff
    IK.NopEffect -> return UseDud  -- all there is
    IK.AndEffect eff1 eff2 -> effectAndEffect recursiveCall source eff1 eff2
    IK.OrEffect eff1 eff2 -> effectOrEffect recursiveCall (bfid sb) eff1 eff2
    IK.SeqEffect effs -> effectSeqEffect recursiveCall effs
    IK.When cond eff ->
      effectWhen recursiveCall source cond eff effActivation
    IK.Unless cond eff ->
      effectUnless recursiveCall source cond eff effActivation
    IK.IfThenElse cond eff1 eff2 ->
      effectIfThenElse recursiveCall source cond eff1 eff2 effActivation
    IK.VerbNoLonger{} -> effectVerbNoLonger effUseAllCopies execSfxSource source
    IK.VerbMsg{} -> effectVerbMsg execSfxSource source
    IK.VerbMsgFail{} -> effectVerbMsgFail execSfxSource source

conditionSem :: MonadServer m
             => ActorId -> IK.Condition -> ActivationFlag -> m Bool
conditionSem source cond effActivation = do
  sb <- getsState $ getActorBody source
  return $! case cond of
    IK.HpLeq n -> bhp sb <= xM n
    IK.HpGeq n -> bhp sb >= xM n
    IK.CalmLeq n -> bcalm sb <= xM n
    IK.CalmGeq n -> bcalm sb >= xM n
    IK.TriggeredBy activationFlag -> activationFlag == effActivation

-- * Individual semantic functions for effects

-- ** Burn

-- Damage from fire. Not affected by armor.
effectBurn :: MonadServerAtomic m
           => Dice.Dice -> ActorId -> ActorId -> ItemId -> m UseResult
effectBurn nDm source target iid = do
  tb <- getsState $ getActorBody target
  totalDepth <- getsState stotalDepth
  Level{ldepth} <- getLevel (blid tb)
  n0 <- rndToAction $ castDice ldepth totalDepth nDm
  let n = max 1 n0  -- avoid 0 and negative burn; validated in content anyway
      deltaHP = - xM n
  sb <- getsState $ getActorBody source
  -- Display the effect more accurately.
  let reportedEffect = IK.Burn $ Dice.intToDice n
  execSfxAtomic $ SfxEffect (bfid sb) target iid reportedEffect deltaHP
  refillHP source target deltaHP
  return UseUp

-- ** Explode

effectExplode :: MonadServerAtomic m
              => m () -> GroupName ItemKind -> ActorId -> ActorId -> Container
              -> m UseResult
effectExplode execSfx cgroup source target containerOrigin = do
  execSfx
  tb <- getsState $ getActorBody target
  oxy@(Point x y) <- getsState $ posFromC containerOrigin
  let itemFreq = [(cgroup, 1)]
      -- Explosion particles are placed among organs of the victim.
      -- TODO: when changing this code, perhaps use @containerOrigin@
      -- in place of @container@, but then remove @borgan@ from several
      -- functions that have the store hardwired.
      container = CActor target COrgan
  -- Power depth of new items unaffected by number of spawned actors.
  Level{ldepth} <- getLevel $ blid tb
  freq <- prepareItemKind 0 ldepth itemFreq
  m2 <- rollAndRegisterItem False ldepth freq container Nothing
  acounter <- getsServer $ fromEnum . sacounter
  let (iid, (ItemFull{itemKind}, (itemK, _))) =
        fromMaybe (error $ "" `showFailure` cgroup) m2
      semiRandom = T.length (IK.idesc itemKind)
      -- We pick a point at the border, not inside, to have a uniform
      -- distribution for the points the line goes through at each distance
      -- from the source. Otherwise, e.g., the points on cardinal
      -- and diagonal lines from the source would be more common.
      projectN k10 n = do
        -- Shape is deterministic for the explosion kind, except that is has
        -- two variants chosen according to time-dependent @veryRandom@.
        -- Choice from the variants prevents diagonal or cardinal directions
        -- being always safe for a given explosion kind.
        let shapeRandom = k10 `xor` (semiRandom + n)
            veryRandom = shapeRandom + acounter + acounter `div` 3
            fuzz = 5 + shapeRandom `mod` 5
            k | n < 16 && n >= 12 = 12
              | n < 12 && n >= 8 = 8
              | n < 8 && n >= 4 = 4
              | otherwise = min n 16  -- fire in groups of 16 including old duds
            psDir4 =
              [ Point (x - 12) (y + 12)
              , Point (x + 12) (y + 12)
              , Point (x - 12) (y - 12)
              , Point (x + 12) (y - 12) ]
            psDir8 =
              [ Point (x - 12) y
              , Point (x + 12) y
              , Point x (y + 12)
              , Point x (y - 12) ]
            psFuzz =
              [ Point (x - 12) $ y + fuzz
              , Point (x + 12) $ y + fuzz
              , Point (x - 12) $ y - fuzz
              , Point (x + 12) $ y - fuzz
              , flip Point (y - 12) $ x + fuzz
              , flip Point (y + 12) $ x + fuzz
              , flip Point (y - 12) $ x - fuzz
              , flip Point (y + 12) $ x - fuzz ]
            randomReverse = if even veryRandom then id else reverse
            ps = take k $ concat $
              randomReverse
                [ zip (repeat True)  -- diagonal particles don't reach that far
                  $ take 4 (drop ((k10 + itemK + fuzz) `mod` 4) $ cycle psDir4)
                , zip (repeat False)  -- only some cardinal reach far
                  $ take 4 (drop ((k10 + n) `mod` 4) $ cycle psDir8) ]
              ++ [zip (repeat True)
                  $ take 8 (drop ((k10 + fuzz) `mod` 8) $ cycle psFuzz)]
        forM_ ps $ \(centerRaw, tpxy) -> do
          let center = centerRaw && itemK >= 8  -- if few, keep them regular
          mfail <- projectFail source target oxy tpxy shapeRandom center
                               iid COrgan True
          case mfail of
            Nothing -> return ()
            Just ProjectBlockTerrain -> return ()
            Just ProjectBlockActor -> return ()
            Just failMsg ->
              execSfxAtomic $ SfxMsgFid (bfid tb) $ SfxUnexpected failMsg
      tryFlying 0 = return ()
      tryFlying k10 = do
        -- Explosion particles were placed among organs of the victim:
        bag2 <- getsState $ borgan . getActorBody target
        -- We stop bouncing old particles when less than two thirds remain,
        -- to prevent hoarding explosives to use only in cramped spaces.
        case EM.lookup iid bag2 of
          Just (n2, _) | n2 * 2 >= itemK `div` 3 -> do
            projectN k10 n2
            tryFlying $ k10 - 1
          _ -> return ()
  -- Some of the particles that fail to take off, bounce off obstacles
  -- up to 10 times in total, trying to fly in different directions.
  tryFlying 10
  bag3 <- getsState $ borgan . getActorBody target
  let mn3 = EM.lookup iid bag3
  -- Give up and destroy the remaining particles, if any.
  maybe (return ()) (\kit -> execUpdAtomic
                             $ UpdLoseItem False iid kit container) mn3
  return UseUp  -- we neglect verifying that at least one projectile got off

-- ** RefillHP

-- Unaffected by armor.
effectRefillHP :: MonadServerAtomic m
               => Int -> ActorId -> ActorId -> ItemId -> m UseResult
effectRefillHP power0 source target iid = do
  sb <- getsState $ getActorBody source
  tb <- getsState $ getActorBody target
  curChalSer <- getsServer $ scurChalSer . soptions
  fact <- getsState $ (EM.! bfid tb) . sfactionD
  let power = if power0 <= -1 then power0 else max 1 power0  -- avoid 0
      deltaHP = xM power
  if cfish curChalSer && deltaHP > 0
     && fhasUI (gkind fact) && bfid sb /= bfid tb
  then do
     execSfxAtomic $ SfxMsgFid (bfid tb) SfxColdFish
     return UseId
  else do
    let reportedEffect = IK.RefillHP power
    execSfxAtomic $ SfxEffect (bfid sb) target iid reportedEffect deltaHP
    refillHP source target deltaHP
    return UseUp

-- ** RefillCalm

effectRefillCalm :: MonadServerAtomic m
                 => m () -> Int -> ActorId -> ActorId -> m UseResult
effectRefillCalm execSfx power0 source target = do
  tb <- getsState $ getActorBody target
  actorMaxSk <- getsState $ getActorMaxSkills target
  let power = if power0 <= -1 then power0 else max 1 power0  -- avoid 0
      rawDeltaCalm = xM power
      calmMax = Ability.getSk Ability.SkMaxCalm actorMaxSk
      serious = rawDeltaCalm <= minusM2 && source /= target && not (bproj tb)
      deltaCalm0 | serious =  -- if overfull, at least cut back to max
                     min rawDeltaCalm (xM calmMax - bcalm tb)
                 | otherwise = rawDeltaCalm
      deltaCalm = if | deltaCalm0 > 0 && bcalm tb > xM 999 ->  -- UI limit
                       tenthM  -- avoid nop, to avoid loops
                     | deltaCalm0 < 0 && bcalm tb < - xM 999 ->
                       -tenthM
                     | otherwise -> deltaCalm0
  execSfx
  updateCalm target deltaCalm
  return UseUp

-- ** Dominate

-- The is another way to trigger domination (the normal way is by zeroed Calm).
-- Calm is here irrelevant. The other conditions are the same.
effectDominate :: MonadServerAtomic m
               => ActorId -> ActorId -> ItemId -> m UseResult
effectDominate source target iid = do
  sb <- getsState $ getActorBody source
  tb <- getsState $ getActorBody target
  if | bproj tb -> return UseDud
     | bfid tb == bfid sb -> return UseDud  -- accidental hit; ignore
     | otherwise -> do
       fact <- getsState $ (EM.! bfid tb) . sfactionD
       hiImpression <- highestImpression tb
       let permitted = case hiImpression of
             Nothing -> False  -- no impression, no domination
             Just (hiImpressionFid, hiImpressionK) ->
                hiImpressionFid == bfid sb
                  -- highest impression needs to be by us
                && (fhasPointman (gkind fact) || hiImpressionK >= 10)
                     -- to tame/hack animal/robot, impress them a lot first
       if permitted then do
         b <- dominateFidSfx source target iid (bfid sb)
         return $! if b then UseUp else UseDud
       else do
         execSfxAtomic $ SfxMsgFid (bfid sb) $ SfxUnimpressed target
         when (source /= target) $
           execSfxAtomic $ SfxMsgFid (bfid tb) $ SfxUnimpressed target
         return UseDud

highestImpression :: MonadServerAtomic m
                  => Actor -> m (Maybe (FactionId, Int))
highestImpression tb = do
  getKind <- getsState $ flip getIidKindServer
  getItem <- getsState $ flip getItemBody
  let isImpression iid =
        maybe False (> 0) $ lookup IK.S_IMPRESSED $ IK.ifreq $ getKind iid
      impressions = EM.filterWithKey (\iid _ -> isImpression iid) $ borgan tb
      f (_, (k, _)) = k
      maxImpression = maximumBy (comparing f) $ EM.assocs impressions
  if EM.null impressions
  then return Nothing
  else case jfid $ getItem $ fst maxImpression of
    Nothing -> return Nothing
    Just fid -> assert (fid /= bfid tb)
                $ return $ Just (fid, fst $ snd maxImpression)

dominateFidSfx :: MonadServerAtomic m
               => ActorId ->  ActorId -> ItemId -> FactionId -> m Bool
dominateFidSfx source target iid fid = do
  tb <- getsState $ getActorBody target
  let !_A = assert (not $ bproj tb) ()
  -- Actors that don't move freely can't be dominated, for otherwise,
  -- when they are the last survivors, they could get stuck and the game
  -- wouldn't end. Also, they are a hassle to guide through the dungeon.
  canTra <- getsState $ canTraverse target
  -- Being pushed protects from domination, for simplicity.
  -- A possible interesting exploit, but much help from content would be needed
  -- to make it practical.
  if isNothing (btrajectory tb) && canTra && bhp tb > 0 then do
    let execSfx = execSfxAtomic $ SfxEffect fid target iid IK.Dominate 0
    execSfx  -- if actor ours, possibly the last occasion to see him
    dominateFid fid source target
    -- If domination resulted in game over, the message won't be seen
    -- before the end game screens, but at least it will be seen afterwards
    -- and browsable in history while inside subsequent game, revealing
    -- the cause of the previous game over. Better than no message at all.
    execSfx  -- see the actor as theirs, unless position not visible
    return True
  else
    return False

dominateFid :: MonadServerAtomic m => FactionId -> ActorId -> ActorId -> m ()
dominateFid fid source target = do
  tb0 <- getsState $ getActorBody target
  -- Game over deduced very early, so no further animation nor message
  -- will appear before game end screens. This is good in that our last actor
  -- that yielded will still be on screen when end game messages roll.
  -- This is bad in that last enemy actor that got dominated by us
  -- may not be on screen and we have no clue how we won until
  -- we see history in the next game. Even worse if our ally dominated
  -- the enemy actor. Then we may never learn. Oh well, that's realism.
  deduceKilled target
  electLeader (bfid tb0) (blid tb0) target
  -- Drop all items so that domiation is not too nasty, especially
  -- if the dominated hero runs off or teleports away with gold
  -- or starts hitting with the most potent artifact weapon in the game.
  -- Drop items while still of the original faction
  -- to mark them on the map for other party members to collect.
  dropAllEquippedItems target tb0
  tb <- getsState $ getActorBody target
  actorMaxSk <- getsState $ getActorMaxSkills target
  getKind <- getsState $ flip getIidKindServer
  let isImpression iid =
        maybe False (> 0) $ lookup IK.S_IMPRESSED $ IK.ifreq $ getKind iid
      dropAllImpressions = EM.filterWithKey (\iid _ -> not $ isImpression iid)
      borganNoImpression = dropAllImpressions $ borgan tb
  -- Actor is not pushed nor projectile, so @sactorTime@ suffices.
  btime <- getsServer
           $ fromJust . lookupActorTime (bfid tb) (blid tb) target . sactorTime
  execUpdAtomic $ UpdLoseActor target tb
  let maxCalm = Ability.getSk Ability.SkMaxCalm actorMaxSk
      maxHp = Ability.getSk Ability.SkMaxHP actorMaxSk
      bNew = tb { bfid = fid
                , bcalm = max (xM 10) $ xM maxCalm `div` 2
                , bhp = min (xM maxHp) $ bhp tb + xM 10
                , borgan = borganNoImpression}
  modifyServer $ \ser ->
    ser {sactorTime = updateActorTime fid (blid tb) target btime
                      $ sactorTime ser}
  execUpdAtomic $ UpdSpotActor target bNew
  -- Focus on the dominated actor, by making him a leader.
  setFreshLeader fid target
  factionD <- getsState sfactionD
  let inGame fact2 = case gquit fact2 of
        Nothing -> True
        Just Status{stOutcome=Camping} -> True
        _ -> False
      gameOver = not $ any inGame $ EM.elems factionD
  -- Avoid the spam of identifying items, if game over.
  unless gameOver $ do
    -- Add some nostalgia for the old faction.
    void $ effectCreateItem (Just $ bfid tb) (Just 10) source target Nothing
                            COrgan IK.S_IMPRESSED IK.timerNone
    -- Identify organs that won't get identified by use.
    getKindId <- getsState $ flip getIidKindIdServer
    let discoverIf (iid, cstore) = do
          let itemKindId = getKindId iid
              c = CActor target cstore
          assert (cstore /= CGround) $
            discoverIfMinorEffects c iid itemKindId
        aic = (btrunk tb, COrgan)
              : filter ((/= btrunk tb) . fst) (getCarriedIidCStore tb)
    mapM_ discoverIf aic

-- | Drop all actor's equipped items.
dropAllEquippedItems :: MonadServerAtomic m => ActorId -> Actor -> m ()
dropAllEquippedItems aid b =
  mapActorCStore_ CEqp
                  (void <$$> dropCStoreItem False False CEqp aid b maxBound)
                  b

-- ** Impress

effectImpress :: MonadServerAtomic m
              => (IK.Effect -> m UseResult) -> m () -> ActorId -> ActorId
              -> m UseResult
effectImpress recursiveCall execSfx source target = do
  sb <- getsState $ getActorBody source
  tb <- getsState $ getActorBody target
  if | bproj tb -> return UseDud
     | bfid tb == bfid sb ->
       -- Unimpress wrt others, but only once. The recursive Sfx suffices.
       recursiveCall $ IK.DropItem 1 1 COrgan IK.S_IMPRESSED
     | otherwise -> do
       -- Actors that don't move freely and so are stupid, can't be impressed.
       canTra <- getsState $ canTraverse target
       if canTra then do
         unless (bhp tb <= 0)
           execSfx  -- avoid spam just before death
         effectCreateItem (Just $ bfid sb) (Just 1) source target Nothing COrgan
                          IK.S_IMPRESSED IK.timerNone
       else return UseDud  -- no message, because common and not crucial

-- ** PutToSleep

effectPutToSleep :: MonadServerAtomic m => m () -> ActorId -> m UseResult
effectPutToSleep execSfx target = do
  tb <- getsState $ getActorBody target
  if | bproj tb -> return UseDud
     | bwatch tb `elem` [WSleep, WWake] ->
         return UseDud  -- can't increase sleep
     | otherwise -> do
       actorMaxSk <- getsState $ getActorMaxSkills target
       if not $ canSleep actorMaxSk then
         return UseId  -- no message about the cause, so at least ID
       else do
         let maxCalm = xM $ Ability.getSk Ability.SkMaxCalm actorMaxSk
             deltaCalm = maxCalm - bcalm tb
         when (deltaCalm > 0) $
           updateCalm target deltaCalm  -- max Calm, but asleep vulnerability
         execSfx
         case bwatch tb of
           WWait n | n > 0 -> do
             nAll <- removeConditionSingle IK.S_BRACED target
             let !_A = assert (nAll == 0) ()
             return ()
           _ -> return ()
         -- Forced sleep. No check if the actor can sleep naturally.
         addSleep target
         return UseUp

-- ** Yell

-- This is similar to 'reqYell', but also mentions that the actor is startled,
-- because, presumably, he yells involuntarily. It doesn't wake him up
-- via Calm instantly, just like yelling in a dream not always does.
effectYell :: MonadServerAtomic m => m () -> ActorId -> m UseResult
effectYell execSfx target = do
  tb <- getsState $ getActorBody target
  if bhp tb <= 0 then  -- avoid yelling corpses
    return UseDud  -- the yell never manifested
  else do
    unless (bproj tb)
      execSfx
    execSfxAtomic $ SfxTaunt False target
    when (not (bproj tb) && deltaBenign (bcalmDelta tb)) $
      execUpdAtomic $ UpdRefillCalm target minusM
    return UseUp

-- ** Summon

-- Note that the Calm expended doesn't depend on the number of actors summoned.
effectSummon :: MonadServerAtomic m
             => GroupName ItemKind -> Dice.Dice -> ItemId
             -> ActorId -> ActorId -> ActivationFlag
             -> m UseResult
effectSummon grp nDm iid source target effActivation = do
  -- Obvious effect, nothing announced.
  sb <- getsState $ getActorBody source
  tb <- getsState $ getActorBody target
  sMaxSk <- getsState $ getActorMaxSkills source
  tMaxSk <- getsState $ getActorMaxSkills target
  totalDepth <- getsState stotalDepth
  Level{ldepth, lbig} <- getLevel (blid tb)
  nFriends <- getsState $ length . friendRegularAssocs (bfid sb) (blid sb)
  discoAspect <- getsState sdiscoAspect
  power0 <- rndToAction $ castDice ldepth totalDepth nDm
  fact <- getsState $ (EM.! bfid sb) . sfactionD
  let arItem = discoAspect EM.! iid
      power = max power0 1  -- KISS, always at least one summon
      -- We put @source@ instead of @target@ and @power@ instead of dice
      -- to make the message more accurate.
      effect = IK.Summon grp $ Dice.intToDice power
      durable = IA.checkFlag Ability.Durable arItem
      warnBothActors warning =
       unless (bproj sb) $ do
         execSfxAtomic $ SfxMsgFid (bfid sb) warning
         when (source /= target) $
           execSfxAtomic $ SfxMsgFid (bfid tb) warning
      deltaCalm = - xM 30
  -- Verify Calm only at periodic activations or if the item is durable.
  -- Otherwise summon uses up the item, which prevents summoning getting
  -- out of hand. I don't verify Calm otherwise, to prevent an exploit
  -- via draining one's calm on purpose when an item with good activation
  -- has a nasty summoning side-effect (the exploit still works on durables).
  if | bproj tb
       || source /= target && not (isFoe (bfid sb) fact (bfid tb)) ->
       return UseDud  -- hitting friends or projectiles to summon is too cheap
     | (effActivation == ActivationPeriodic || durable) && not (bproj sb)
       && (bcalm sb < - deltaCalm || not (calmEnough sb sMaxSk)) -> do
       warnBothActors $ SfxSummonLackCalm source
       return UseId
     | nFriends >= 20 -> do
       -- We assume the actor tries to summon his teammates or allies.
       -- As he repeats such summoning, he is going to bump into this limit.
       -- If he summons others, see the next condition.
       warnBothActors $ SfxSummonTooManyOwn source
       return UseId
     | EM.size lbig >= 200 -> do  -- lower than the 300 limit for spawning
       -- Even if the actor summons foes, he is prevented from exploiting it
       -- too many times and stopping natural monster spawning on the level
       -- (e.g., by filling the level with harmless foes).
       warnBothActors $ SfxSummonTooManyAll source
       return UseId
     | otherwise -> do
       unless (bproj sb) $ updateCalm source deltaCalm
       localTime <- getsState $ getLocalTime (blid tb)
       -- Make sure summoned actors start acting after the victim.
       let actorTurn = ticksPerMeter $ gearSpeed tMaxSk
           targetTime = timeShift localTime actorTurn
           afterTime = timeShift targetTime $ Delta timeClip
       -- Mark as summoned to prevent immediate chain summoning.
       -- Summon from current depth, not deeper due to many spawns already.
       anySummoned <- addManyActors True 0 [(grp, 1)] (blid tb) afterTime
                                    (Just $ bpos tb) power
       if anySummoned then do
         execSfxAtomic $ SfxEffect (bfid sb) source iid effect 0
         return UseUp
       else do
         -- We don't display detailed warnings when @addAnyActor@ fails,
         -- e.g., because the actor groups can't be generated on a given level.
         -- However, we at least don't claim any summoning happened
         -- and we offer a general summoning failure messages.
         warnBothActors $ SfxSummonFailure source
         return UseId

-- ** Ascend

-- Note that projectiles can be teleported, too, for extra fun.
effectAscend :: MonadServerAtomic m
             => (IK.Effect -> m UseResult)
             -> m () -> Bool -> ActorId -> ActorId -> Container
             -> m UseResult
effectAscend recursiveCall execSfx up source target container = do
  b1 <- getsState $ getActorBody target
  pos <- getsState $ posFromC container
  let lid1 = blid b1
  destinations <- getsState $ whereTo lid1 pos up . sdungeon
  sb <- getsState $ getActorBody source
  actorMaxSk <- getsState $ getActorMaxSkills target
  if | source /= target && Ability.getSk Ability.SkMove actorMaxSk <= 0 -> do
       execSfxAtomic $ SfxMsgFid (bfid sb) SfxTransImpossible
       when (source /= target) $
         execSfxAtomic $ SfxMsgFid (bfid b1) SfxTransImpossible
       return UseId
     | actorWaits b1 && source /= target -> do
       execSfxAtomic $ SfxMsgFid (bfid sb) $ SfxBracedImmune target
       when (source /= target) $
         execSfxAtomic $ SfxMsgFid (bfid b1) $ SfxBracedImmune target
       return UseId
     | null destinations -> do
       execSfxAtomic $ SfxMsgFid (bfid sb) SfxLevelNoMore
       when (source /= target) $
         execSfxAtomic $ SfxMsgFid (bfid b1) SfxLevelNoMore
       -- We keep it useful even in shallow dungeons.
       recursiveCall $ IK.Teleport 30  -- powerful teleport
     | otherwise -> do
       (lid2, pos2) <- rndToAction $ oneOf destinations
       execSfx
       mbtime_bOld <-
         getsServer $ lookupActorTime (bfid b1) lid1 target . sactorTime
       mbtimeTraj_bOld <-
         getsServer $ lookupActorTime (bfid b1) lid1 target . strajTime
       pos3 <- findStairExit (bfid sb) up lid2 pos2
       let switch1 = void $ switchLevels1 (target, b1)
           switch2 = do
             -- Make the initiator of the stair move the leader,
             -- to let him clear the stairs for others to follow.
             let mlead = if bproj b1 then Nothing else Just target
             -- Move the actor to where the inhabitants were, if any.
             switchLevels2 lid2 pos3 (target, b1)
                           mbtime_bOld mbtimeTraj_bOld mlead
       -- The actor will be added to the new level,
       -- but there can be other actors at his new position.
       inhabitants <- getsState $ posToAidAssocs pos3 lid2
       case inhabitants of
         (_, b2) : _ | not $ bproj b1 -> do
           -- Alert about the switch.
           execSfxAtomic $ SfxMsgFid (bfid sb) SfxLevelPushed
           -- Only tell one pushed player, even if many actors, because then
           -- they are projectiles, so not too important.
           when (source /= target) $
             execSfxAtomic $ SfxMsgFid (bfid b2) SfxLevelPushed
           -- Move the actor out of the way.
           switch1
           -- Move the inhabitants out of the way and to where the actor was.
           let moveInh inh = do
                 -- Preserve the old leader, since the actor is pushed,
                 -- so possibly has nothing worhwhile to do on the new level
                 -- (and could try to switch back, if made a leader,
                 -- leading to a loop).
                 mbtime_inh <-
                   getsServer $ lookupActorTime (bfid (snd inh)) lid2 (fst inh)
                                . sactorTime
                 mbtimeTraj_inh <-
                   getsServer $ lookupActorTime (bfid (snd inh)) lid2 (fst inh)
                                . strajTime
                 inhMLead <- switchLevels1 inh
                 switchLevels2 lid1 (bpos b1) inh
                               mbtime_inh mbtimeTraj_inh inhMLead
           mapM_ moveInh inhabitants
           -- Move the actor to his destination.
           switch2
         _ -> do  -- no inhabitants or the stair-taker a projectile
           switch1
           switch2
       return UseUp

findStairExit :: MonadStateRead m
              => FactionId -> Bool -> LevelId -> Point -> m Point
findStairExit side moveUp lid pos = do
  COps{coTileSpeedup} <- getsState scops
  fact <- getsState $ (EM.! side) . sfactionD
  lvl <- getLevel lid
  let defLanding = uncurry Vector $ if moveUp then (1, 0) else (-1, 0)
      center = uncurry Vector $ if moveUp then (-1, 0) else (1, 0)
      (mvs2, mvs1) = break (== defLanding) moves
      mvs = center : filter (/= center) (mvs1 ++ mvs2)
      ps = filter (Tile.isWalkable coTileSpeedup . (lvl `at`))
           $ map (shift pos) mvs
      posOcc :: State -> Int -> Point -> Bool
      posOcc s k p = case posToAidAssocs p lid s of
        [] -> k == 0
        (_, b) : _ | bproj b -> k == 3
        (_, b) : _ | isFoe side fact (bfid b) -> k == 1  -- non-proj foe
        _ -> k == 2  -- moving a non-projectile friend
  unocc <- getsState posOcc
  case concatMap (\k -> filter (unocc k) ps) [0..3] of
    [] -> error $ "" `showFailure` ps
    posRes : _ -> return posRes

switchLevels1 :: MonadServerAtomic m => (ActorId, Actor) -> m (Maybe ActorId)
switchLevels1 (aid, bOld) = do
  let side = bfid bOld
  mleader <- getsState $ gleader . (EM.! side) . sfactionD
  -- Prevent leader pointing to a non-existing actor.
  mlead <-
    if not (bproj bOld) && isJust mleader then do
      execUpdAtomic $ UpdLeadFaction side mleader Nothing
      return mleader
        -- outside of a client we don't know the real tgt of aid, hence fst
    else return Nothing
  -- Remove the actor from the old level.
  -- Onlookers see somebody disappear suddenly.
  -- @UpdDestroyActor@ is too loud, so use @UpdLoseActor@ instead.
  execUpdAtomic $ UpdLoseActor aid bOld
  return mlead

switchLevels2 ::MonadServerAtomic m
              => LevelId -> Point -> (ActorId, Actor)
              -> Maybe Time -> Maybe Time -> Maybe ActorId
              -> m ()
switchLevels2 lidNew posNew (aid, bOld) mbtime_bOld mbtimeTraj_bOld mlead = do
  let lidOld = blid bOld
      side = bfid bOld
  let !_A = assert (lidNew /= lidOld `blame` "stairs looped" `swith` lidNew) ()
  -- Sync actor's items' timeouts with the new local time of the level.
  -- We need to sync organs and equipment due to periodic activations,
  -- but also due to timeouts after use, e.g., for some weapons
  -- (they recharge also in the stash; however, this doesn't encourage
  -- micromanagement for periodic items, because the timeout is randomised
  -- upon move to equipment).
  --
  -- We don't rebase timeouts for items in stash, because they are
  -- used by many actors on levels with different local times,
  -- so there is no single rebase that would match all.
  -- This is not a big problem: after a single use by an actor the timeout is
  -- set to his current local time, so further uses by that actor have
  -- not anomalously short or long recharge times. If the recharge time
  -- is very long, the player has an option of moving the item away from stash
  -- and back, to reset the timeout. An abuse is possible when recently
  -- used item is put from equipment to stash and at once used on another level
  -- taking advantage of local time difference, but this only works once
  -- and using the item back again at the original level makes the recharge
  -- time longer, in turn.
  timeOld <- getsState $ getLocalTime lidOld
  timeLastActive <- getsState $ getLocalTime lidNew
  let delta = timeLastActive `timeDeltaToFrom` timeOld
      computeNewTimeout :: ItemQuant -> ItemQuant
      computeNewTimeout (k, it) = (k, map (shiftItemTimer delta) it)
      rebaseTimeout :: ItemBag -> ItemBag
      rebaseTimeout = EM.map computeNewTimeout
      bNew = bOld { blid = lidNew
                  , bpos = posNew
                  , boldpos = Just posNew  -- new level, new direction
                  , borgan = rebaseTimeout $ borgan bOld
                  , beqp = rebaseTimeout $ beqp bOld }
      shiftByDelta = (`timeShift` delta)
  -- Sync the actor time with the level time.
  -- This time shift may cause a double move of a foe of the same speed,
  -- but this is OK --- the foe didn't have a chance to move
  -- before, because the arena went inactive, so he moves now one more time.
  maybe (return ())
        (\btime_bOld ->
    modifyServer $ \ser ->
      ser {sactorTime = updateActorTime (bfid bNew) lidNew aid
                                        (shiftByDelta btime_bOld)
                        $ sactorTime ser})
        mbtime_bOld
  maybe (return ())
        (\btime_bOld ->
    modifyServer $ \ser ->
      ser {strajTime = updateActorTime (bfid bNew) lidNew aid
                                       (shiftByDelta btime_bOld)
                       $ strajTime ser})
        mbtimeTraj_bOld
  -- Materialize the actor at the new location.
  -- Onlookers see somebody appear suddenly. The actor himself
  -- sees new surroundings and has to reset his perception.
  execUpdAtomic $ UpdSpotActor aid bNew
  forM_ mlead $
    -- The leader is fresh in the sense that he's on a new level
    -- and so doesn't have up to date Perception.
    setFreshLeader side

-- ** Escape

-- | The faction leaves the dungeon.
effectEscape :: MonadServerAtomic m => m () -> ActorId -> ActorId -> m UseResult
effectEscape execSfx source target = do
  -- Obvious effect, nothing announced.
  sb <- getsState $ getActorBody source
  tb <- getsState $ getActorBody target
  let fid = bfid tb
  fact <- getsState $ (EM.! fid) . sfactionD
  if | bproj tb ->
       return UseDud  -- basically a misfire
     | not (fcanEscape $ gkind fact) -> do
       execSfxAtomic $ SfxMsgFid (bfid sb) SfxEscapeImpossible
       when (source /= target) $
         execSfxAtomic $ SfxMsgFid (bfid tb) SfxEscapeImpossible
       return UseId
     | otherwise -> do
       execSfx
       deduceQuits (bfid tb) $ Status Escape (fromEnum $ blid tb) Nothing
       return UseUp

-- ** Paralyze

-- | Advance target actor time by this many time clips. Not by actor moves,
-- to hurt fast actors more.
effectParalyze :: MonadServerAtomic m
               => m () -> Dice.Dice -> ActorId -> ActorId -> m UseResult
effectParalyze execSfx nDm source target = do
  tb <- getsState $ getActorBody target
  if bproj tb then return UseDud  -- shortcut for speed
  else paralyze execSfx nDm source target

paralyze :: MonadServerAtomic m
         => m () -> Dice.Dice -> ActorId -> ActorId -> m UseResult
paralyze execSfx nDm source target = do
  tb <- getsState $ getActorBody target
  totalDepth <- getsState stotalDepth
  Level{ldepth} <- getLevel (blid tb)
  power0 <- rndToAction $ castDice ldepth totalDepth nDm
  let power = max power0 1  -- KISS, avoid special case
  actorStasis <- getsServer sactorStasis
  if ES.member target actorStasis then do
    sb <- getsState $ getActorBody source
    execSfxAtomic $ SfxMsgFid (bfid sb) SfxStasisProtects
    when (source /= target) $
      execSfxAtomic $ SfxMsgFid (bfid tb) SfxStasisProtects
    return UseId
  else do
    execSfx
    let t = timeDeltaScale (Delta timeClip) power
    -- Only the normal time, not the trajectory time, is affected.
    modifyServer $ \ser ->
      ser { sactorTime = ageActor (bfid tb) (blid tb) target t
                         $ sactorTime ser
          , sactorStasis = ES.insert target (sactorStasis ser) }
              -- actor's time warped, so he is in stasis,
              -- immune to further warps
    return UseUp

-- ** ParalyzeInWater

-- | Advance target actor time by this many time clips. Not by actor moves,
-- to hurt fast actors more. Due to water, so resistable.
effectParalyzeInWater :: MonadServerAtomic m
                      => m () -> Dice.Dice -> ActorId -> ActorId -> m UseResult
effectParalyzeInWater execSfx nDm source target = do
  tb <- getsState $ getActorBody target
  if bproj tb then return UseDud else do  -- shortcut for speed
    actorMaxSk <- getsState $ getActorMaxSkills target
    let swimmingOrFlying = max (Ability.getSk Ability.SkSwimming actorMaxSk)
                               (Ability.getSk Ability.SkFlying actorMaxSk)
    if Dice.supDice nDm > swimmingOrFlying
    then paralyze execSfx nDm source target  -- no help at all
    else  -- fully resisted
      -- Don't spam:
      -- sb <- getsState $ getActorBody source
      -- execSfxAtomic $ SfxMsgFid (bfid sb) SfxWaterParalysisResisted
      return UseId

-- ** InsertMove

-- | Give target actor the given number of tenths of extra move. Don't give
-- an absolute amount of time units, to benefit slow actors more.
effectInsertMove :: MonadServerAtomic m
                 => m () -> Dice.Dice -> ActorId -> ActorId -> m UseResult
effectInsertMove execSfx nDm source target = do
  tb <- getsState $ getActorBody target
  actorMaxSk <- getsState $ getActorMaxSkills target
  totalDepth <- getsState stotalDepth
  Level{ldepth} <- getLevel (blid tb)
  actorStasis <- getsServer sactorStasis
  power0 <- rndToAction $ castDice ldepth totalDepth nDm
  let power = max power0 1  -- KISS, avoid special case
      actorTurn = ticksPerMeter $ gearSpeed actorMaxSk
      t = timeDeltaScale (timeDeltaPercent actorTurn 10) (-power)
  if | bproj tb -> return UseDud  -- shortcut for speed
     | ES.member target actorStasis -> do
       sb <- getsState $ getActorBody source
       execSfxAtomic $ SfxMsgFid (bfid sb) SfxStasisProtects
       when (source /= target) $
         execSfxAtomic $ SfxMsgFid (bfid tb) SfxStasisProtects
       return UseId
     | otherwise -> do
       execSfx
       -- Only the normal time, not the trajectory time, is affected.
       modifyServer $ \ser ->
         ser { sactorTime = ageActor (bfid tb) (blid tb) target t
                            $ sactorTime ser
             , sactorStasis = ES.insert target (sactorStasis ser) }
                 -- actor's time warped, so he is in stasis,
                 -- immune to further warps
       return UseUp

-- ** Teleport

-- | Teleport the target actor.
-- Note that projectiles can be teleported, too, for extra fun.
effectTeleport :: MonadServerAtomic m
               => m () -> Dice.Dice -> ActorId -> ActorId -> m UseResult
effectTeleport execSfx nDm source target = do
  sb <- getsState $ getActorBody source
  tb <- getsState $ getActorBody target
  actorMaxSk <- getsState $ getActorMaxSkills target
  if | source /= target && Ability.getSk Ability.SkMove actorMaxSk <= 0 -> do
       execSfxAtomic $ SfxMsgFid (bfid sb) SfxTransImpossible
       when (source /= target) $
         execSfxAtomic $ SfxMsgFid (bfid tb) SfxTransImpossible
       return UseId
     | source /= target && actorWaits tb -> do
         -- immune only against not own effects, to enable teleport
         -- as beneficial's necklace drawback; also consistent
         -- with sleep not protecting
       execSfxAtomic $ SfxMsgFid (bfid sb) $ SfxBracedImmune target
       when (source /= target) $
         execSfxAtomic $ SfxMsgFid (bfid tb) $ SfxBracedImmune target
       return UseId
     | otherwise -> do
       COps{coTileSpeedup} <- getsState scops
       totalDepth <- getsState stotalDepth
       lvl@Level{ldepth} <- getLevel (blid tb)
       range <- rndToAction $ castDice ldepth totalDepth nDm
       let spos = bpos tb
           dMinMax !delta !pos =
             let d = chessDist spos pos
             in d >= range - delta && d <= range + delta
           dist !delta !pos _ = dMinMax delta pos
       mtpos <- rndToAction $ findPosTry 200 lvl
         (\p !t -> Tile.isWalkable coTileSpeedup t
                   && not (Tile.isNoActor coTileSpeedup t)
                   && not (occupiedBigLvl p lvl)
                   && not (occupiedProjLvl p lvl))
         [ dist 1
         , dist $ 1 + range `div` 9
         , dist $ 1 + range `div` 7
         , dist $ 1 + range `div` 5
         , dist 5
         , dist 7
         , dist 9
         ]
       case mtpos of
         Nothing -> do  -- really very rare, so debug
           debugPossiblyPrint
             "Server: effectTeleport: failed to find any free position"
           execSfxAtomic $ SfxMsgFid (bfid sb) SfxTransImpossible
           when (source /= target) $
             execSfxAtomic $ SfxMsgFid (bfid tb) SfxTransImpossible
           return UseId
         Just tpos -> do
           execSfx
           execUpdAtomic $ UpdMoveActor target spos tpos
           return UseUp

-- ** CreateItem

effectCreateItem :: MonadServerAtomic m
                 => Maybe FactionId -> Maybe Int -> ActorId -> ActorId
                 -> Maybe ItemId -> CStore -> GroupName ItemKind -> IK.TimerDice
                 -> m UseResult
effectCreateItem jfidRaw mcount source target miidOriginal store grp tim = do
 tb <- getsState $ getActorBody target
 if bproj tb && store == COrgan  -- other stores OK not to lose possible loot
 then return UseDud  -- don't make a projectile hungry, etc.
 else do
  cops <- getsState scops
  sb <- getsState $ getActorBody source
  actorMaxSk <- getsState $ getActorMaxSkills target
  totalDepth <- getsState stotalDepth
  lvlTb <- getLevel (blid tb)
  let -- If the number of items independent of depth in @mcount@,
      -- make also the timer, the item kind choice and aspects
      -- independent of depth, via fixing the generation depth of the item
      -- to @totalDepth@. Prime example of provided @mcount@ is crafting.
      -- TODO: base this on a resource that can be consciously spent,
      -- not on a skill that grows over time or that only one actor
      -- maxes out and so needs to always be chosen for crafting.
      -- See https://www.reddit.com/r/roguelikedev/comments/phukcq/game_design_question_how_to_base_item_generation/
      depth = if isJust mcount then totalDepth else ldepth lvlTb
      fscale unit nDm = do
        k0 <- rndToAction $ castDice depth totalDepth nDm
        let k = max 1 k0  -- KISS, don't freak out if dice permit 0
        return $! timeDeltaScale unit k
      fgame = fscale (Delta timeTurn)
      factor nDm = do
        -- A bit added to make sure length 1 effect doesn't randomly
        -- end, or not, before the end of first turn, which would make,
        -- e.g., hasting, useless. This needs to be higher than 10%
        -- to compensate for overhead of animals, etc. (no leaders).
        let actorTurn =
              timeDeltaPercent (ticksPerMeter $ gearSpeed actorMaxSk) 111
        fscale actorTurn nDm
  delta <- IK.foldTimer (return $ Delta timeZero) fgame factor tim
  let c = CActor target store
  bagBefore <- getsState $ getBodyStoreBag tb store
  uniqueSet <- getsServer suniqueSet
  -- Power depth of new items unaffected by number of spawned actors, so 0.
  let freq = newItemKind cops uniqueSet [(grp, 1)] depth totalDepth 0
  m2 <- rollItemAspect freq depth
  case m2 of
    NoNewItem -> return UseDud  -- e.g., unique already generated
    NewItem _ itemKnownRaw itemFullRaw (kRaw, itRaw) -> do
      -- Avoid too many different item identifiers (one for each faction)
      -- for blasts or common item generating tiles. Conditions are
      -- allowed to be duplicated, because they provide really useful info
      -- (perpetrator). However, if timer is none, they are not duplicated
      -- to make sure that, e.g., poisons stack with each other regardless
      -- of perpetrator and we don't get "no longer poisoned" message
      -- while still poisoned due to another faction. With timed aspects,
      -- e.g., slowness, the message is less misleading, and it's interesting
      -- that I'm twice slower due to aspects from two factions and not
      -- as deadly as being poisoned at twice the rate from two factions.
      let jfid = if store == COrgan && not (IK.isTimerNone tim)
                    || grp == IK.S_IMPRESSED
                 then jfidRaw
                 else Nothing
          ItemKnown kindIx arItem _ = itemKnownRaw
          (itemKnown, itemFull) =
            ( ItemKnown kindIx arItem jfid
            , itemFullRaw {itemBase = (itemBase itemFullRaw) {jfid}} )
      itemRev <- getsServer sitemRev
      let mquant = case HM.lookup itemKnown itemRev of
            Nothing -> Nothing
            Just iid -> (iid,) <$> iid `EM.lookup` bagBefore
      case mquant of
        Just (iid, (_, afterIt@(timer : rest))) | not $ IK.isTimerNone tim -> do
          -- Already has such items and timer change requested, so only increase
          -- the timer of the first item by the delta, but don't create items.
          let newIt = shiftItemTimer delta timer : rest
          if afterIt /= newIt then do
            execUpdAtomic $ UpdTimeItem iid c afterIt newIt
            -- It's hard for the client to tell this timer change from charge
            -- use, timer reset on pickup, etc., so we create the msg manually.
            -- Sending to both involved factions lets the player notice
            -- both the extensions he caused and suffered. Other faction causing
            -- that on themselves or on others won't be noticed. TMI.
            execSfxAtomic $ SfxMsgFid (bfid sb)
                          $ SfxTimerExtended target iid store delta
            when (bfid sb /= bfid tb) $
              execSfxAtomic $ SfxMsgFid (bfid tb)
                            $ SfxTimerExtended target iid store delta
            return UseUp
          else return UseDud  -- probably incorrect content, but let it be
        _ -> do
          localTime <- getsState $ getLocalTime (blid tb)
          let newTimer = createItemTimer localTime delta
              extraIt k = if IK.isTimerNone tim
                          then itRaw  -- don't break @applyPeriodicLevel@
                          else replicate k newTimer
                                 -- randomized and overwritten in @registerItem@
                                 -- if an organ or created in equipment
              kitNew = case mcount of
                Just itemK -> (itemK, extraIt itemK)
                Nothing -> (kRaw, extraIt kRaw)
          case miidOriginal of
            Just iidOriginal | store /= COrgan ->
              execSfxAtomic $ SfxMsgFid (bfid tb)
                            $ SfxItemYield iidOriginal (fst kitNew) (blid tb)
            _ -> return ()
          -- No such items or some items, but void delta, so create items.
          -- If it's, e.g., a periodic poison, the new items will stack with any
          -- already existing items.
          iid <- registerItem True (itemFull, kitNew) itemKnown c
          -- If created not on the ground, ID it, because it won't be on pickup.
          -- If ground and stash coincide, unindentified item enters stash,
          -- so will be identified when equipped, used or dropped
          -- and picked again.
          if isJust mcount  -- not a random effect, so probably crafting
             && not (IA.isHumanTrinket (itemKind itemFull))
          then execUpdAtomic $ UpdDiscover c iid (itemKindId itemFull) arItem
          else when (store /= CGround) $
            discoverIfMinorEffects c iid (itemKindId itemFull)
          return UseUp

-- ** DestroyItem

-- | Make the target actor destroy items in a store from the given group.
-- The item that caused the effect itself is *not* immune, because often
-- the item needs to destroy itself, e.g., to model wear and tear.
-- In such a case, the item may need to be identified, in a container,
-- when it no longer exists, at least in the container. This is OK.
-- Durable items are not immune, unlike the tools in @ConsumeItems@.
effectDestroyItem :: MonadServerAtomic m
                  => m () -> Int -> Int -> CStore -> ActorId
                  -> GroupName ItemKind
                  -> m UseResult
effectDestroyItem execSfx ngroup kcopy store target grp = do
  tb <- getsState $ getActorBody target
  is <- allGroupItems store grp target
  if null is then return UseDud
  else do
    execSfx
    urs <- mapM (uncurry (dropCStoreItem True True store target tb kcopy))
                (take ngroup is)
    return $! case urs of
      [] -> UseDud  -- there was no effects
      _ -> maximum urs

-- | Drop a single actor's item (though possibly multiple copies).
-- Note that if there are multiple copies, at most one explodes
-- to avoid excessive carnage and UI clutter (let's say,
-- the multiple explosions interfere with each other or perhaps
-- larger quantities of explosives tend to be packaged more safely).
-- Note also that @OnSmash@ effects are activated even if item discharged.
dropCStoreItem :: MonadServerAtomic m
               => Bool -> Bool -> CStore -> ActorId -> Actor -> Int
               -> ItemId -> ItemQuant
               -> m UseResult
dropCStoreItem verbose destroy store aid b kMax iid (k, _) = do
 let c = CActor aid store
 bag0 <- getsState $ getContainerBag c
  -- @OnSmash@ effects of previous items may remove next items, so better check.
 if iid `EM.notMember` bag0 then return UseDud else do
  itemFull <- getsState $ itemToFull iid
  let arItem = aspectRecordFull itemFull
      fragile = IA.checkFlag Ability.Fragile arItem
      durable = IA.checkFlag Ability.Durable arItem
      isDestroyed = destroy
                    || bproj b && (bhp b <= 0 && not durable || fragile)
                    || store == COrgan  -- just as organs are destroyed at death
                                        -- but also includes conditions
  if isDestroyed then do
    let effApplyFlags = EffApplyFlags
          { effToUse            = EffBare
              -- the embed could be combined at this point but @iid@ cannot
          , effVoluntary        = True
              -- we don't know if it's effVoluntary, so we conservatively assume
              -- it is and we blame @aid@
          , effUseAllCopies     = kMax >= k
          , effKineticPerformed = False
          , effActivation       = ActivationOnSmash
          , effMayDestroy       = True
          }
    void $ effectAndDestroyAndAddKill effApplyFlags aid aid aid iid c itemFull
    -- One copy was destroyed (or none if the item was discharged),
    -- so let's mop up.
    bag <- getsState $ getContainerBag c
    maybe (return ())
          (\(k1, it) -> do
             let destroyedSoFar = k - k1
                 k2 = min (kMax - destroyedSoFar) k1
                 kit2 = (k2, take k2 it)
                 -- Don't spam if the effect already probably made noise
                 -- and also the number could be surprising to the player.
                 verbose2 = verbose && k1 == k
             when (k2 > 0) $
               execUpdAtomic $ UpdDestroyItem verbose2 iid (itemBase itemFull)
                                              kit2 c)
          (EM.lookup iid bag)
    return UseUp
  else do
    cDrop <- pickDroppable False aid b  -- drop over fog, etc.
    mvCmd <- generalMoveItem verbose iid (min kMax k) (CActor aid store) cDrop
    mapM_ execUpdAtomic mvCmd
    return UseUp

pickDroppable :: MonadStateRead m => Bool -> ActorId -> Actor -> m Container
pickDroppable respectNoItem aid b = do
  cops@COps{coTileSpeedup} <- getsState scops
  lvl <- getLevel (blid b)
  let validTile t = not (respectNoItem && Tile.isNoItem coTileSpeedup t)
  if validTile $ lvl `at` bpos b
  then return $! CActor aid CGround
  else do
    let ps = nearbyFreePoints cops lvl validTile (bpos b)
    return $! case filter (adjacent $ bpos b) $ take 8 ps of
      [] -> CActor aid CGround  -- fallback; still correct, though not ideal
      pos : _ -> CFloor (blid b) pos

-- ** ConsumeItems

-- | Make the target actor destroy the given items, if all present,
-- or none at all, if any is missing. To be used in crafting.
-- The item that caused the effect itself is not considered (any copies).
effectConsumeItems :: MonadServerAtomic m
                   => m () -> ItemId -> ActorId
                   -> [(Int, GroupName ItemKind)]
                   -> [(Int, GroupName ItemKind)]
                   -> m UseResult
effectConsumeItems execSfx iidOriginal target tools0 raw0 = do
  kitAssG <- getsState $ kitAssocs target [CGround]
  let kitAss = listToolsToConsume kitAssG []  -- equipment too dangerous to use
      is = filter ((/= iidOriginal) . fst . snd) kitAss
      grps0 = map (\(x, y) -> (False, x, y)) tools0  -- apply if durable
              ++ map (\(x, y) -> (True, x, y)) raw0  -- destroy always
      (bagsToLose3, iidsToApply3, grps3) =
        foldl' subtractIidfromGrps (EM.empty, [], grps0) is
  if null grps3 then do
    execSfx
    consumeItems target bagsToLose3 iidsToApply3
    return UseUp
  else return UseDud

consumeItems :: MonadServerAtomic m
             => ActorId -> EM.EnumMap CStore ItemBag
             -> [(CStore, (ItemId, ItemFull))]
             -> m ()
consumeItems target bagsToLose iidsToApply = do
  COps{coitem} <- getsState scops
  tb <- getsState $ getActorBody target
  arTrunk <- getsState $ (EM.! btrunk tb) . sdiscoAspect
  let isBlast = IA.checkFlag Ability.Blast arTrunk
      identifyStoreBag store bag =
        mapM_ (identifyStoreIid store) $ EM.keys bag
      identifyStoreIid store iid = do
        discoAspect2 <- getsState sdiscoAspect
          -- might have changed due to embedded items invocations
        itemKindId <- getsState $ getIidKindIdServer iid
        let arItem = discoAspect2 EM.! iid
            c = CActor target store
            itemKind = okind coitem itemKindId
        unless (IA.isHumanTrinket itemKind) $  -- a hack
          execUpdAtomic $ UpdDiscover c iid itemKindId arItem
  -- We don't invoke @OnSmash@ effects, so we avoid the risk
  -- of the first removed item displacing the actor, destroying
  -- or scattering some pending items ahead of time, etc.
  -- The embed should provide any requisite fireworks instead.
  forM_ (EM.assocs bagsToLose) $ \(store, bagToLose) ->
    unless (EM.null bagToLose) $ do
      identifyStoreBag store bagToLose
      -- Not @UpdLoseItemBag@, to be verbose.
      -- The bag is small, anyway.
      let c = CActor target store
      itemD <- getsState sitemD
      mapWithKeyM_ (\iid kit -> do
                      let verbose = not isBlast  -- no spam
                          item = itemD EM.! iid
                      execUpdAtomic $ UpdDestroyItem verbose iid item kit c)
                   bagToLose
  -- But afterwards we do apply normal effects of durable items,
  -- even if the actor or other items displaced in the process,
  -- as long as a number of the items is still there.
  -- So if a harmful double-purpose tool-component is both to be used
  -- and destroyed, it will be lost, but at least it won't harm anybody.
  let applyItemIfPresent (store, (iid, itemFull)) = do
        let c = CActor target store
        bag <- getsState $ getContainerBag c
        when (iid `EM.member` bag) $ do
          execSfxAtomic $ SfxApply target iid
          -- Treated as if the actor only activated the item on himself,
          -- without kinetic damage, to avoid the exploit of wearing armor
          -- when using tools or transforming terrain.
          -- Also, timeouts of the item ignored to prevent exploit
          -- by discharging the item before using it.
          let effApplyFlags = EffApplyFlags
                { effToUse            = EffBare  -- crafting not intended
                , effVoluntary        = True
                , effUseAllCopies     = False
                , effKineticPerformed = False
                , effActivation       = ActivationConsume
                , effMayDestroy       = False
                }
          void $ effectAndDestroyAndAddKill effApplyFlags
                                            target target target iid c itemFull
  mapM_ applyItemIfPresent iidsToApply

-- ** DropItem

-- | Make the target actor drop items in a store from the given group.
-- The item that caused the effect itself is immune (any copies).
effectDropItem :: MonadServerAtomic m
               => m () -> ItemId -> Int -> Int -> CStore
               -> GroupName ItemKind -> ActorId
               -> m UseResult
effectDropItem execSfx iidOriginal ngroup kcopy store grp target = do
  tb <- getsState $ getActorBody target
  fact <- getsState $ (EM.! bfid tb) . sfactionD
  isRaw <- allGroupItems store grp target
  curChalSer <- getsServer $ scurChalSer . soptions
  factionD <- getsState sfactionD
  let is = filter ((/= iidOriginal) . fst) isRaw
  if | bproj tb || null is -> return UseDud
     | ngroup == maxBound && kcopy == maxBound
       && store `elem` [CStash, CEqp]
       && fhasGender (gkind fact)  -- hero in Allure's decontamination chamber
       && (cdiff curChalSer == 1   -- at lowest difficulty for its faction
           && any (fhasUI . gkind . snd)
                  (filter (\(fi, fa) -> isFriend fi fa (bfid tb))
                          (EM.assocs factionD))
           || cdiff curChalSer == difficultyBound
              && any (fhasUI . gkind  . snd)
                     (filter (\(fi, fa) -> isFoe fi fa (bfid tb))
                             (EM.assocs factionD))) ->
{-
A hardwired hack, because AI heroes don't cope with Allure's decontamination
chamber; beginners may struggle too, so this is trigered by difficulty.
- AI heroes don't switch leader to the hero past laboratory to equip
weapons from stash between the in-lab hero picks up the loot pile
and himself enters the decontamination chamber
- the items of the last actor would be lost anyway, unless AI
is taught the foolproof solution of this puzzle, which is yet a bit more
specific than the two abilities above
-}
       return UseUp
     | otherwise -> do
       unless (store == COrgan) execSfx
       urs <- mapM (uncurry (dropCStoreItem True False store target tb kcopy))
                   (take ngroup is)
       return $! case urs of
         [] -> UseDud  -- there was no effects
         _ -> maximum urs

-- ** Recharge and Discharge

effectRecharge :: forall m. MonadServerAtomic m
               => Bool -> m () -> ItemId -> Int -> Dice.Dice -> ActorId
               -> m UseResult
effectRecharge reducingCooldown execSfx iidOriginal n0 dice target = do
 tb <- getsState $ getActorBody target
 if bproj tb then return UseDud else do  -- slows down, but rarely any effect
  localTime <- getsState $ getLocalTime (blid tb)
  totalDepth <- getsState stotalDepth
  Level{ldepth} <- getLevel $ blid tb
  power <- rndToAction $ castDice ldepth totalDepth dice
  let timeUnit = if reducingCooldown
                 then absoluteTimeNegate timeClip
                 else timeClip
      delta = timeDeltaScale (Delta timeUnit) power
      localTimer = createItemTimer localTime (Delta timeZero)
      addToCooldown :: CStore -> (Int, UseResult) -> (ItemId, ItemFullKit)
                    -> m (Int, UseResult)
      addToCooldown _ (0, ur) _ = return (0, ur)
      addToCooldown store (n, ur) (iid, (_, (k0, itemTimers0))) = do
        let itemTimers = filter (charging localTime) itemTimers0
            kt = length itemTimers
            lenToShift = min n $ if reducingCooldown then kt else k0 - kt
            (itToShift, itToKeep) =
              if reducingCooldown
              then splitAt lenToShift itemTimers
              else (replicate lenToShift localTimer, itemTimers)
            -- No problem if this overcharges; equivalent to pruned timer.
            it2 = map (shiftItemTimer delta) itToShift ++ itToKeep
        if itemTimers0 == it2
        then return (n, ur)
        else do
          let c = CActor target store
          execUpdAtomic $ UpdTimeItem iid c itemTimers0 it2
          return (n - lenToShift, UseUp)
      selectWeapon i@(iid, (itemFull, _)) (weapons, others) =
        let arItem = aspectRecordFull itemFull
        in if | IA.aTimeout arItem == 0
                || iid == iidOriginal -> (weapons, others)
              | IA.checkFlag Ability.Meleeable arItem -> (i : weapons, others)
              | otherwise -> (weapons, i : others)
      partitionWeapon = foldr selectWeapon ([],[])
      ignoreCharges = True  -- handled above depending on @reducingCooldown@
      benefits = Nothing  -- only raw damage counts (client knows benefits)
      sortWeapons ass =
        map (\(_, _, _, _, iid, itemFullKit) -> (iid, itemFullKit))
        $ strongestMelee ignoreCharges benefits localTime ass
  eqpAss <- getsState $ kitAssocs target [CEqp]
  let (eqpAssWeapons, eqpAssOthers) = partitionWeapon eqpAss
  organAss <- getsState $ kitAssocs target [COrgan]
  let (organAssWeapons, organAssOthers) = partitionWeapon organAss
  (nEqpWeapons, urEqpWeapons) <-
    foldM (addToCooldown CEqp) (n0, UseDud)
    $ sortWeapons eqpAssWeapons
  (nOrganWeapons, urOrganWeapons) <-
    foldM (addToCooldown COrgan) (nEqpWeapons, urEqpWeapons)
    $ sortWeapons organAssWeapons
  (nEqpOthers, urEqpOthers) <-
    foldM (addToCooldown CEqp) (nOrganWeapons, urOrganWeapons) eqpAssOthers
  (_nOrganOthers, urOrganOthers) <-
    foldM (addToCooldown COrgan) (nEqpOthers, urEqpOthers) organAssOthers
  if urOrganOthers == UseDud then return UseDud
  else do
    execSfx
    return UseUp

-- ** PolyItem

-- Can't apply to the item itself (any copies).
effectPolyItem :: MonadServerAtomic m
               => m () -> ItemId -> ActorId -> m UseResult
effectPolyItem execSfx iidOriginal target = do
  tb <- getsState $ getActorBody target
  let cstore = CGround
  kitAss <- getsState $ kitAssocs target [cstore]
  case filter ((/= iidOriginal) . fst) kitAss of
    [] -> do
      execSfxAtomic $ SfxMsgFid (bfid tb) SfxPurposeNothing
      -- Do not spam the source actor player about the failures.
      return UseId
    (iid, ( itemFull@ItemFull{itemBase, itemKindId, itemKind}
          , (itemK, itemTimer) )) : _ -> do
      let arItem = aspectRecordFull itemFull
          maxCount = Dice.supDice $ IK.icount itemKind
      if | IA.checkFlag Ability.Unique arItem -> do
           execSfxAtomic $ SfxMsgFid (bfid tb) SfxPurposeUnique
           return UseId
         | maybe True (<= 0) $ lookup IK.COMMON_ITEM $ IK.ifreq itemKind -> do
           execSfxAtomic $ SfxMsgFid (bfid tb) SfxPurposeNotCommon
           return UseId
         | itemK < maxCount -> do
           execSfxAtomic $ SfxMsgFid (bfid tb)
                         $ SfxPurposeTooFew maxCount itemK
           return UseId
         | otherwise -> do
           -- Only the required number of items is used up, not all of them.
           let c = CActor target cstore
               kit = (maxCount, take maxCount itemTimer)
           execSfx
           identifyIid iid c itemKindId itemKind
           execUpdAtomic $ UpdDestroyItem True iid itemBase kit c
           effectCreateItem (Just $ bfid tb) Nothing
                            target target Nothing cstore
                            IK.COMMON_ITEM IK.timerNone

-- ** RerollItem

-- Can't apply to the item itself (any copies).
effectRerollItem :: forall m . MonadServerAtomic m
                 => m () -> ItemId -> ActorId -> m UseResult
effectRerollItem execSfx iidOriginal target = do
  COps{coItemSpeedup} <- getsState scops
  tb <- getsState $ getActorBody target
  let cstore = CGround  -- if ever changed, call @discoverIfMinorEffects@
  kitAss <- getsState $ kitAssocs target [cstore]
  case filter ((/= iidOriginal) . fst) kitAss of
    [] -> do
      execSfxAtomic $ SfxMsgFid (bfid tb) SfxRerollNothing
      -- Do not spam the source actor player about the failures.
      return UseId
    (iid, ( ItemFull{ itemBase, itemKindId, itemKind
                    , itemDisco=ItemDiscoFull itemAspect }
          , (_, itemTimer) )) : _ ->
      if IA.kmConst $ getKindMean itemKindId coItemSpeedup then do
        execSfxAtomic $ SfxMsgFid (bfid tb) SfxRerollNotRandom
        return UseId
      else do
        let c = CActor target cstore
            kit = (1, take 1 itemTimer)  -- prevent micromanagement
            freq = pure (IK.HORROR, itemKindId, itemKind)
        execSfx
        identifyIid iid c itemKindId itemKind
        execUpdAtomic $ UpdDestroyItem False iid itemBase kit c
        totalDepth <- getsState stotalDepth
        let roll100 :: Int -> m (ItemKnown, ItemFull)
            roll100 n = do
              -- Not only rerolled, but at highest depth possible,
              -- resulting in highest potential for bonuses.
              m2 <- rollItemAspect freq totalDepth
              case m2 of
                NoNewItem ->
                  error "effectRerollItem: can't create rerolled item"
                NewItem _ itemKnown@(ItemKnown _ ar2 _) itemFull _ ->
                  if ar2 == itemAspect && n > 0
                  then roll100 (n - 1)
                  else return (itemKnown, itemFull)
        (itemKnown, itemFull) <- roll100 100
        void $ registerItem True (itemFull, kit) itemKnown c
        return UseUp
    _ -> error "effectRerollItem: server ignorant about an item"

-- ** DupItem

-- Can't apply to the item itself (any copies).
effectDupItem :: MonadServerAtomic m => m () -> ItemId -> ActorId -> m UseResult
effectDupItem execSfx iidOriginal target = do
  tb <- getsState $ getActorBody target
  let cstore = CGround  -- beware of other options, e.g., creating in eqp
                        -- and not setting timeout to a random value
  kitAss <- getsState $ kitAssocs target [cstore]
  case filter ((/= iidOriginal) . fst) kitAss of
    [] -> do
      execSfxAtomic $ SfxMsgFid (bfid tb) SfxDupNothing
      -- Do not spam the source actor player about the failures.
      return UseId
    (iid, ( itemFull@ItemFull{itemKindId, itemKind}
          , _ )) : _ -> do
      let arItem = aspectRecordFull itemFull
      if | IA.checkFlag Ability.Unique arItem -> do
           execSfxAtomic $ SfxMsgFid (bfid tb) SfxDupUnique
           return UseId
         | maybe False (> 0) $ lookup IK.VALUABLE $ IK.ifreq itemKind -> do
           execSfxAtomic $ SfxMsgFid (bfid tb) SfxDupValuable
           return UseId
         | otherwise -> do
           let c = CActor target cstore
           execSfx
           identifyIid iid c itemKindId itemKind
           let slore = IA.loreFromContainer arItem c
           modifyServer $ \ser ->
             ser {sgenerationAn = EM.adjust (EM.insertWith (+) iid 1) slore
                                            (sgenerationAn ser)}
           execUpdAtomic $ UpdCreateItem True iid (itemBase itemFull)
                                         quantSingle c
           return UseUp

-- ** Identify

effectIdentify :: MonadServerAtomic m
               => m () -> ItemId -> ActorId -> m UseResult
effectIdentify execSfx iidOriginal target = do
  COps{coItemSpeedup} <- getsState scops
  discoAspect <- getsState sdiscoAspect
  -- The actor that causes the application does not determine what item
  -- is identifiable, becuase it's the target actor that identifies
  -- his possesions.
  tb <- getsState $ getActorBody target
  sClient <- getsServer $ (EM.! bfid tb) . sclientStates
  let tryFull store as = case as of
        [] -> return False
        (iid, _) : rest | iid == iidOriginal -> tryFull store rest  -- don't id itself
        (iid, ItemFull{itemBase, itemKindId, itemKind}) : rest -> do
          let arItem = discoAspect EM.! iid
              kindIsKnown = case jkind itemBase of
                IdentityObvious _ -> True
                IdentityCovered ix _ -> ix `EM.member` sdiscoKind sClient
          if iid `EM.member` sdiscoAspect sClient  -- already fully identified
             || IA.isHumanTrinket itemKind  -- hack; keep them non-identified
             || store == CGround && IA.onlyMinorEffects arItem itemKind
               -- will be identified when picked up, so don't bother
             || IA.kmConst (getKindMean itemKindId coItemSpeedup)
                && kindIsKnown
               -- constant aspects and known kind; no need to identify further;
               -- this should normally not be needed, since clients should
               -- identify such items for free
          then tryFull store rest
          else do
            let c = CActor target store
            execSfx
            identifyIid iid c itemKindId itemKind
            return True
      tryStore stores = case stores of
        [] -> do
          execSfxAtomic $ SfxMsgFid (bfid tb) SfxIdentifyNothing
          return UseId  -- the message tells it's ID effect
        store : rest -> do
          allAssocs <- getsState $ fullAssocs target [store]
          go <- tryFull store allAssocs
          if go then return UseUp else tryStore rest
  tryStore [CGround, CStash, CEqp]

-- The item need not be in the container. It's used for a message only.
identifyIid :: MonadServerAtomic m
            => ItemId -> Container -> ContentId ItemKind -> ItemKind -> m ()
identifyIid iid c itemKindId itemKind =
  unless (IA.isHumanTrinket itemKind) $ do
    discoAspect <- getsState sdiscoAspect
    execUpdAtomic $ UpdDiscover c iid itemKindId $ discoAspect EM.! iid

-- ** Detect

effectDetect :: MonadServerAtomic m
             => m () -> IK.DetectKind -> Int -> ActorId -> Container
             -> m UseResult
effectDetect execSfx d radius target container = do
  COps{coitem, coTileSpeedup} <- getsState scops
  b <- getsState $ getActorBody target
  lvl <- getLevel $ blid b
  sClient <- getsServer $ (EM.! bfid b) . sclientStates
  let lvlClient = (EM.! blid b) . sdungeon $ sClient
  s <- getState
  getKind <- getsState $ flip getIidKindServer
  factionD <- getsState sfactionD
  let lootPredicate p =
        p `EM.member` lfloor lvl
        || (case posToBigAssoc p (blid b) s of
              Nothing -> False
              Just (_, body) ->
                let belongings = EM.keys (beqp body)  -- shared stash ignored
                in any belongingIsLoot belongings)
        || any embedHasLoot (EM.keys $ getEmbedBag (blid b) p s)
      itemKindIsLoot = isNothing . lookup IK.UNREPORTED_INVENTORY . IK.ifreq
      belongingIsLoot iid = itemKindIsLoot $ getKind iid
      embedHasLoot iid = any effectHasLoot $ IK.ieffects $ getKind iid
      reported acc _ _ itemKind = acc && itemKindIsLoot itemKind
      effectHasLoot (IK.CreateItem _ cstore grp _) =
        cstore `elem` [CGround, CStash, CEqp]
        && ofoldlGroup' coitem grp reported True
      effectHasLoot IK.PolyItem = True
      effectHasLoot IK.RerollItem = True
      effectHasLoot IK.DupItem = True
      effectHasLoot (IK.AtMostOneOf l) = any effectHasLoot l
      effectHasLoot (IK.OneOf l) = any effectHasLoot l
      effectHasLoot (IK.OnSmash eff) = effectHasLoot eff
      effectHasLoot (IK.OnUser eff) = effectHasLoot eff
      effectHasLoot (IK.AndEffect eff1 eff2) =
        effectHasLoot eff1 || effectHasLoot eff2
      effectHasLoot (IK.OrEffect eff1 eff2) =
        effectHasLoot eff1 || effectHasLoot eff2
      effectHasLoot (IK.SeqEffect effs) =
        any effectHasLoot effs
      effectHasLoot (IK.When _ eff) = effectHasLoot eff
      effectHasLoot (IK.Unless _ eff) = effectHasLoot eff
      effectHasLoot (IK.IfThenElse _ eff1 eff2) =
        effectHasLoot eff1 || effectHasLoot eff2
      effectHasLoot _ = False
      stashPredicate p = any (onStash p) $ EM.assocs factionD
      onStash p (fid, fact) = case gstash fact of
        Just (lid, pos) -> pos == p && lid == blid b && fid /= bfid b
        Nothing -> False
      (predicate, action) = case d of
        IK.DetectAll -> (const True, const $ return False)
        IK.DetectActor -> ((`EM.member` lbig lvl), const $ return False)
        IK.DetectLoot -> (lootPredicate, const $ return False)
        IK.DetectExit ->
          let (ls1, ls2) = lstair lvl
          in ((`elem` ls1 ++ ls2 ++ lescape lvl), const $ return False)
        IK.DetectHidden ->
          let predicateH p = let tClient = lvlClient `at` p
                                 tServer = lvl `at` p
                             in Tile.isHideAs coTileSpeedup tServer
                                && tClient /= tServer
                -- the actor searches only tiles he doesn't know already,
                -- preventing misleading messages (and giving less information
                -- to eavesdropping parties)
              revealEmbed p = do
                embeds <- getsState $ getEmbedBag (blid b) p
                unless (EM.null embeds) $
                  execUpdAtomic $ UpdSpotItemBag True (CEmbed (blid b) p) embeds
              actionH l = do
                pos <- getsState $ posFromC container
                let f p = when (p /= pos) $ do
                      let t = lvl `at` p
                      execUpdAtomic $ UpdSearchTile target p t
                      -- This is safe searching; embedded items
                      -- are not triggered, but they are revealed.
                      revealEmbed p
                      case EM.lookup p $ lentry lvl of
                        Nothing -> return ()
                        Just entry ->
                          execUpdAtomic $ UpdSpotEntry (blid b) [(p, entry)]
                mapM_ f l
                return $! not $ null l
          in (predicateH, actionH)
        IK.DetectEmbed -> ((`EM.member` lembed lvl), const $ return False)
        IK.DetectStash -> (stashPredicate, const $ return False)
  effectDetectX d predicate action execSfx radius target

-- This is not efficient at all, so optimize iff detection is added
-- to periodic organs or common periodic items or often activated embeds.
effectDetectX :: MonadServerAtomic m
              => IK.DetectKind -> (Point -> Bool) -> ([Point] -> m Bool)
              -> m () -> Int -> ActorId -> m UseResult
effectDetectX d predicate action execSfx radius target = do
  COps{corule=RuleContent{rWidthMax, rHeightMax}} <- getsState scops
  b <- getsState $ getActorBody target
  sperFidOld <- getsServer sperFid
  let perOld = sperFidOld EM.! bfid b EM.! blid b
      Point x0 y0 = bpos b
      perList = filter predicate
        [ Point x y
        | y <- [max 0 (y0 - radius) .. min (rHeightMax - 1) (y0 + radius)]
        , x <- [max 0 (x0 - radius) .. min (rWidthMax - 1) (x0 + radius)]
        ]
      extraPer = emptyPer {psight = PerVisible $ ES.fromDistinctAscList perList}
      inPer = diffPer extraPer perOld
  unless (nullPer inPer) $ do
    -- Perception is modified on the server and sent to the client
    -- together with all the revealed info.
    let perNew = addPer inPer perOld
        fper = EM.adjust (EM.insert (blid b) perNew) (bfid b)
    modifyServer $ \ser -> ser {sperFid = fper $ sperFid ser}
    execSendPer (bfid b) (blid b) emptyPer inPer perNew
  pointsModified <- action perList
  if not (nullPer inPer) || pointsModified then do
    execSfx
    -- Perception is reverted. This is necessary to ensure save and restore
    -- doesn't change game state.
    unless (nullPer inPer) $ do
      modifyServer $ \ser -> ser {sperFid = sperFidOld}
      execSendPer (bfid b) (blid b) inPer emptyPer perOld
  else
    execSfxAtomic $ SfxMsgFid (bfid b) $ SfxVoidDetection d
  return UseUp  -- even if nothing spotted, in itself it's still useful data

-- ** SendFlying

-- | Send the target actor flying like a projectile. If the actors are adjacent,
-- the vector is directed outwards, if no, inwards, if it's the same actor,
-- boldpos is used, if it can't, a random outward vector of length 10
-- is picked.
effectSendFlying :: MonadServerAtomic m
                 => m () -> IK.ThrowMod -> ActorId -> ActorId -> Container
                 -> Maybe Bool
                 -> m UseResult
effectSendFlying execSfx IK.ThrowMod{..} source target container modePush = do
  v <- sendFlyingVector source target container modePush
  sb <- getsState $ getActorBody source
  tb <- getsState $ getActorBody target
  let eps = 0
      fpos = bpos tb `shift` v
      isEmbed = case container of
        CEmbed{} -> True
        _ -> False
  if bhp tb <= 0  -- avoid dragging around corpses
     || bproj tb && isEmbed then  -- flying projectiles can't slip on the floor
    return UseDud  -- the impact never manifested
  else if actorWaits tb
          && source /= target
          && isNothing (btrajectory tb) then do
    execSfxAtomic $ SfxMsgFid (bfid sb) $ SfxBracedImmune target
    when (source /= target) $
      execSfxAtomic $ SfxMsgFid (bfid tb) $ SfxBracedImmune target
    return UseUp  -- waste it to prevent repeated throwing at immobile actors
  else do
   case bresenhamsLineAlgorithm eps (bpos tb) fpos of
    Nothing -> error $ "" `showFailure` (fpos, tb)
    Just [] -> error $ "projecting from the edge of level"
                       `showFailure` (fpos, tb)
    Just (pos : rest) -> do
      weightAssocs <- getsState $ fullAssocs target [CEqp, COrgan]
      let weight = sum $ map (IK.iweight . itemKind . snd) weightAssocs
          path = bpos tb : pos : rest
          (trajectory, (speed, _)) =
            -- Note that the @ThrowMod@ aspect of the actor's trunk is ignored.
            computeTrajectory weight throwVelocity throwLinger path
          ts = Just (trajectory, speed)
      -- Old and new trajectories are not added; the old one is replaced.
      if btrajectory tb == ts
      then return UseId  -- e.g., actor is too heavy; but a jerk is noticeable
      else do
        execSfx
        execUpdAtomic $ UpdTrajectory target (btrajectory tb) ts
        -- If propeller is a projectile, it pushes involuntarily,
        -- so its originator is to blame.
        -- However, we can't easily see whether a pushed non-projectile actor
        -- pushed another due to colliding or voluntarily, so we assign
        -- blame to him.
        originator <- if bproj sb
                      then getsServer $ EM.findWithDefault source source
                                        . strajPushedBy
                      else return source
        modifyServer $ \ser ->
          ser {strajPushedBy = EM.insert target originator $ strajPushedBy ser}
        -- In case of pre-existing pushing, don't touch the time
        -- so that the pending @advanceTimeTraj@ can do its job
        -- (it will, because non-empty trajectory is here set, unless, e.g.,
        -- subsequent effects from the same item change the trajectory).
        when (isNothing $ btrajectory tb) $ do
          -- Set flying time to almost now, so that the push happens ASAP,
          -- because it's the first one, so almost no delay is needed.
          localTime <- getsState $ getLocalTime (blid tb)
          -- But add a slight overhead to avoid displace-slide loops
          -- of 3 actors in a line. However, add even more overhead
          -- to normal actor move, so that it doesn't manage to land
          -- a hit before it flies away safely.
          let overheadTime = timeShift localTime (Delta timeClip)
              doubleClip = timeDeltaScale (Delta timeClip) 2
          modifyServer $ \ser ->
            ser { strajTime =
                    updateActorTime (bfid tb) (blid tb) target overheadTime
                    $ strajTime ser
                , sactorTime =
                    ageActor (bfid tb) (blid tb) target doubleClip
                    $ sactorTime ser }
        return UseUp

sendFlyingVector :: MonadServerAtomic m
                 => ActorId -> ActorId -> Container -> Maybe Bool -> m Vector
sendFlyingVector source target container modePush = do
  sb <- getsState $ getActorBody source
  if source == target then do
    pos <- getsState $ posFromC container
    lid <- getsState $ lidFromC container
    let (start, end) =
          -- Without the level the pushing stair trap moved actor back upstairs.
          if bpos sb /= pos && blid sb == lid
          then (bpos sb, pos)
          else (fromMaybe (bpos sb) (boldpos sb), bpos sb)
    if start == end then rndToAction $ do
      z <- randomR (-10, 10)
      oneOf [Vector 10 z, Vector (-10) z, Vector z 10, Vector z (-10)]
    else do
      let pushV = vectorToFrom end start
          pullV = vectorToFrom start end
      return $! case modePush of
                  Just True -> pushV
                  Just False -> pullV
                  Nothing -> pushV
  else do
    tb <- getsState $ getActorBody target
    let pushV = vectorToFrom (bpos tb) (bpos sb)
        pullV = vectorToFrom (bpos sb) (bpos tb)
    return $! case modePush of
                Just True -> pushV
                Just False -> pullV
                Nothing | adjacent (bpos sb) (bpos tb) -> pushV
                Nothing -> pullV

-- ** ApplyPerfume

effectApplyPerfume :: MonadServerAtomic m => m () -> ActorId -> m UseResult
effectApplyPerfume execSfx target = do
  tb <- getsState $ getActorBody target
  Level{lsmell} <- getLevel $ blid tb
  unless (EM.null lsmell) $ do
    execSfx
    let f p fromSm = execUpdAtomic $ UpdAlterSmell (blid tb) p fromSm timeZero
    mapWithKeyM_ f lsmell
  return UseUp  -- even if no smell before, the perfume is noticeable

-- ** AtMostOneOf

effectAtMostOneOf :: MonadServerAtomic m
                  => (IK.Effect -> m UseResult) -> [IK.Effect] -> m UseResult
effectAtMostOneOf recursiveCall l = do
  chosen <- rndToAction $ oneOf l
  recursiveCall chosen
  -- no @execSfx@, because the individual effect sents it

-- ** OneOf

effectOneOf :: MonadServerAtomic m
            => (IK.Effect -> m UseResult) -> [IK.Effect] -> m UseResult
effectOneOf recursiveCall l = do
  shuffled <- rndToAction $ shuffle l
  let f eff result = do
        ur <- recursiveCall eff
        -- We stop at @UseId@ activation and in this ways avoid potentially
        -- many calls to fizzling effects that only spam a failure message
        -- and ID the item.
        if ur == UseDud then result else return ur
  foldr f (return UseDud) shuffled
  -- no @execSfx@, because the individual effect sents it

-- ** AndEffect

effectAndEffect :: forall m. MonadServerAtomic m
                => (IK.Effect -> m UseResult) -> ActorId
                -> IK.Effect -> IK.Effect
                -> m UseResult
effectAndEffect recursiveCall source eff1@IK.ConsumeItems{} eff2 = do
  -- So far, this is the only idiom used for crafting. If others appear,
  -- either formalize it by a specialized crafting effect constructor
  -- or add here and to effect printing code.
  sb <- getsState $ getActorBody source
  curChalSer <- getsServer $ scurChalSer . soptions
  fact <- getsState $ (EM.! bfid sb) . sfactionD
  if cgoods curChalSer && fhasUI (gkind fact) then do
    execSfxAtomic $ SfxMsgFid (bfid sb) SfxReadyGoods
    return UseId
  else effectAndEffectSem recursiveCall eff1 eff2

effectAndEffect recursiveCall _ eff1 eff2 =
  effectAndEffectSem recursiveCall eff1 eff2

effectAndEffectSem :: forall m. MonadServerAtomic m
                   => (IK.Effect -> m UseResult) -> IK.Effect -> IK.Effect
                   -> m UseResult
effectAndEffectSem recursiveCall eff1 eff2 = do
  ur1 <- recursiveCall eff1
  if ur1 == UseUp
  then recursiveCall eff2
  else return ur1
  -- No @execSfx@, because individual effects sent them.

-- ** OrEffect

effectOrEffect :: forall m. MonadServerAtomic m
               => (IK.Effect -> m UseResult)
               -> FactionId -> IK.Effect -> IK.Effect
               -> m UseResult
effectOrEffect recursiveCall fid eff1 eff2 = do
  curChalSer <- getsServer $ scurChalSer . soptions
  fact <- getsState $ (EM.! fid) . sfactionD
  case eff1 of
    IK.AndEffect IK.ConsumeItems{} _ | cgoods curChalSer
                                       && fhasUI (gkind fact) -> do
      -- Stop forbidden crafting ASAP to avoid spam.
      execSfxAtomic $ SfxMsgFid fid SfxReadyGoods
      return UseId
    _ -> do
      ur1 <- recursiveCall eff1
      if ur1 == UseUp
      then return UseUp
      else recursiveCall eff2
             -- no @execSfx@, because individual effects sent them

-- ** SeqEffect

effectSeqEffect :: forall m. MonadServerAtomic m
                => (IK.Effect -> m UseResult) -> [IK.Effect]
                -> m UseResult
effectSeqEffect recursiveCall effs = do
  mapM_ (void <$> recursiveCall) effs
  return UseUp
  -- no @execSfx@, because individual effects sent them

-- ** When

effectWhen :: forall m. MonadServerAtomic m
           => (IK.Effect -> m UseResult) -> ActorId
           -> IK.Condition -> IK.Effect -> ActivationFlag
           -> m UseResult
effectWhen recursiveCall source cond eff effActivation = do
  go <- conditionSem source cond effActivation
  if go then recursiveCall eff else return UseDud

-- ** Unless

effectUnless :: forall m. MonadServerAtomic m
             => (IK.Effect -> m UseResult) -> ActorId
             -> IK.Condition -> IK.Effect -> ActivationFlag
             -> m UseResult
effectUnless recursiveCall source cond eff effActivation = do
  go <- conditionSem source cond effActivation
  if not go then recursiveCall eff else return UseDud

-- ** IfThenElse

effectIfThenElse :: forall m. MonadServerAtomic m
                 => (IK.Effect -> m UseResult) -> ActorId
                 -> IK.Condition -> IK.Effect -> IK.Effect -> ActivationFlag
                 -> m UseResult
effectIfThenElse recursiveCall source cond eff1 eff2 effActivation = do
  c <- conditionSem source cond effActivation
  if c then recursiveCall eff1 else recursiveCall eff2

-- ** VerbNoLonger

effectVerbNoLonger :: MonadServerAtomic m
                   => Bool -> m () -> ActorId -> m UseResult
effectVerbNoLonger effUseAllCopies execSfx source = do
  b <- getsState $ getActorBody source
  when (effUseAllCopies  -- @UseUp@ ensures that if all used, all destroyed
        && not (bproj b))  -- no spam when projectiles activate
    execSfx  -- announce that all copies have run out (or whatever message)
  return UseUp  -- help to destroy the copy, even if not all used up

-- ** VerbMsg

effectVerbMsg :: MonadServerAtomic m => m () -> ActorId -> m UseResult
effectVerbMsg execSfx source = do
  b <- getsState $ getActorBody source
  unless (bproj b) execSfx  -- don't spam when projectiles activate
  return UseUp  -- announcing always successful and this helps
                -- to destroy the item

-- ** VerbMsgFail

effectVerbMsgFail :: MonadServerAtomic m => m () -> ActorId -> m UseResult
effectVerbMsgFail execSfx source = do
  b <- getsState $ getActorBody source
  unless (bproj b) execSfx  -- don't spam when projectiles activate
  return UseId  -- not @UseDud@ so that @OneOf@ doesn't ignore it