public bool TakeDamage(Damage dmg,string cause_of_death) { //returns true if still alive if(dmg.amount == 0){ return true; } bool damage_dealt = false; int old_hp = curhp; if(curhp <= 0 && dmg.amount < 1000){ //then we're dealing with a corpse, and they don't take normal amounts of damage return true; } bool ice_removed = false; if(dmg.amount < 1000){ if(HasAttr(AttrType.FROZEN) && (dmg.major_damage || dmg.type == DamageType.FIRE)){ //this should ignore bleeding and poison, but not searing attrs[AttrType.FROZEN] -= dmg.amount; if(attrs[AttrType.FROZEN] <= 0){ attrs[AttrType.FROZEN] = 0; B.Add("The ice breaks! ",this); ice_removed = true; } dmg.amount = 0; if(dmg.type == DamageType.FIRE && HasAttr(AttrType.FROZEN)){ attrs[AttrType.FROZEN] = 0; B.Add("The ice melts! ",this); ice_removed = true; } } if(dmg.type == DamageType.FIRE && HasAttr(AttrType.OIL_COVERED)){ if(HasAttr(AttrType.IMMUNE_BURNING)){ B.Add("The oil burns off of " + the_name + ". ",this); attrs[AttrType.OIL_COVERED] = 0; } else{ B.Add(You("catch",true) + " fire! ",this); ApplyBurning(); } } if(dmg.type == DamageType.COLD && HasAttr(AttrType.SLIMED)){ attrs[AttrType.SLIMED] = 0; B.Add("The slime freezes and falls from " + the_name + ". ",this); } if(!dmg.major_damage && HasAttr(AttrType.MINOR_IMMUNITY,AttrType.SHIELDED)){ return true; } if(HasAttr(AttrType.MECHANICAL_SHIELD)){ B.Add(Your() + " shield moves to protect it from harm. ",this); return true; } if(dmg.major_damage){ if(HasAttr(AttrType.BLOCKING)){ B.Add(You("block") + "! ",this); //todo: extra effects will go here eventually attrs[AttrType.BLOCKING]--; return true; } else{ if(HasAttr(AttrType.SHIELDED)){ B.Add(Your() + " shield flashes! ",this); attrs[AttrType.SHIELDED]--; return true; } else{ if(HasAttr(AttrType.VULNERABLE)){ attrs[AttrType.VULNERABLE] = 0; if(this == player){ B.Add("Ouch! "); } else{ B.Add(YouAre() + " devastated! ",this); } foreach(Event e in Q.list){ if(!e.dead && e.target == this && e.type == EventType.REMOVE_ATTR && e.attr == AttrType.VULNERABLE){ e.dead = true; } } dmg.amount += R.Roll(3,6); } } } } if(HasAttr(AttrType.INVULNERABLE)){ dmg.amount = 0; } /*if(HasAttr(AttrType.TOUGH) && dmg.damclass == DamageClass.PHYSICAL){ dmg.amount -= 2; } if(HasAttr(AttrType.ARCANE_SHIELDED)){ if(attrs[AttrType.ARCANE_SHIELDED] >= dmg.amount){ attrs[AttrType.ARCANE_SHIELDED] -= dmg.amount; dmg.amount = 0; } else{ dmg.amount -= attrs[AttrType.ARCANE_SHIELDED]; attrs[AttrType.ARCANE_SHIELDED] = 0; } if(!HasAttr(AttrType.ARCANE_SHIELDED)){ B.Add(Your() + " shield fades. ",this); } } if(dmg.damclass == DamageClass.MAGICAL){ dmg.amount -= TotalSkill(SkillType.SPIRIT) / 2; } if(HasAttr(AttrType.DAMAGE_REDUCTION) && dmg.amount > 5){ dmg.amount = 5; }*/ if(dmg.amount > 15 && magic_trinkets.Contains(MagicTrinketType.BELT_OF_WARDING)){ dmg.amount = 15; B.Add(Your() + " " + MagicTrinket.Name(MagicTrinketType.BELT_OF_WARDING) + " softens the blow. ",this); } dmg.amount -= attrs[AttrType.DAMAGE_RESISTANCE]; switch(dmg.type){ case DamageType.NORMAL: if(dmg.amount > 0){ curhp -= dmg.amount; damage_dealt = true; } else{ if(!ice_removed){ B.Add(YouAre() + " undamaged. ",this); } } break; case DamageType.MAGIC: if(dmg.amount > 0){ curhp -= dmg.amount; damage_dealt = true; } else{ if(!ice_removed){ B.Add(YouAre() + " unharmed. ",this); } } break; case DamageType.FIRE: { if(HasAttr(AttrType.IMMUNE_FIRE)){ dmg.amount = 0; //B.Add(the_name + " is immune! ",this); if(HasFeat(FeatType.BOILING_BLOOD) && attrs[AttrType.BLOOD_BOILED] == 5){ RefreshDuration(AttrType.IMMUNE_FIRE,1000); RefreshDuration(AttrType.BLOOD_BOILED,1000,"Your blood cools. "); } } if(dmg.amount > 0){ curhp -= dmg.amount; damage_dealt = true; } else{ if((this == player || dmg.amount > 1) && !ice_removed && !HasAttr(AttrType.IMMUNE_FIRE) && !HasAttr(AttrType.JUST_SEARED)){ B.Add(YouAre() + " unburnt. ",this); } } break; } case DamageType.COLD: { if(HasAttr(AttrType.IMMUNE_COLD)){ dmg.amount = 0; //B.Add(YouAre() + " unharmed. ",this); } if(dmg.amount > 0){ curhp -= dmg.amount; damage_dealt = true; if(type == ActorType.GIANT_SLUG){ B.Add("The cold leaves " + the_name + " vulnerable. ",this); RefreshDuration(AttrType.VULNERABLE,R.Between(7,13)*100,the_name + " is no longer vulnerable. ",this); } } else{ if(!ice_removed && !HasAttr(AttrType.IMMUNE_COLD)){ B.Add(YouAre() + " unharmed. ",this); } } break; } case DamageType.ELECTRIC: { if(HasAttr(AttrType.IMMUNE_ELECTRICITY)){ dmg.amount = 0; } if(dmg.amount > 0){ curhp -= dmg.amount; damage_dealt = true; } else{ if(!ice_removed && !HasAttr(AttrType.IMMUNE_ELECTRICITY)){ B.Add(YouAre() + " unharmed. ",this); } } break; } case DamageType.POISON: if(HasAttr(AttrType.NONLIVING)){ dmg.amount = 0; } if(dmg.amount > 0){ curhp -= dmg.amount; damage_dealt = true; if(type == ActorType.PLAYER){ B.Add("The poison burns! "); } else{ if(R.Roll(1,5) == 5 && !HasAttr(AttrType.PLANTLIKE)){ //hmm B.Add(the_name + " shudders. ",this); } } } break; case DamageType.NONE: break; } } else{ if(curhp > 0){ curhp = 0; } } /*if(dmg.source != null && dmg.source == player && dmg.damclass == DamageClass.PHYSICAL && resisted && !cause_of_death.Contains("arrow")){ Help.TutorialTip(TutorialTopic.Resistance); }*/ if(damage_dealt){ Interrupt(); attrs[AttrType.AMNESIA_STUN] = 0; if(dmg.major_damage){ recover_time = Q.turn + 500; attrs[AttrType.BANDAGED] = 0; } if(HasAttr(AttrType.ASLEEP)){ attrs[AttrType.ASLEEP] = 0; attrs[AttrType.JUST_AWOKE] = 1; if(this == player){ Input.FlushInput(); B.Add("You wake up. "); } } if(dmg.source != null){ if(type != ActorType.PLAYER && dmg.source != this && !HasAttr(AttrType.CONFUSED)){ target = dmg.source; target_location = M.tile[dmg.source.row,dmg.source.col]; if(dmg.source.IsHiddenFrom(this)){ player_visibility_duration = -1; } if(type == ActorType.CAVERN_HAG && dmg.source == player){ attrs[AttrType.COOLDOWN_2] = 1; } if(type == ActorType.CRUSADING_KNIGHT && dmg.source == player && !HasAttr(AttrType.COOLDOWN_1) && !M.wiz_lite && !CanSee(player) && curhp > 0){ List<string> verb = new List<string>{"Show yourself","Reveal yourself","Unfold thyself","Present yourself","Unveil yourself","Make yourself known"}; List<string> adjective = new List<string>{"despicable","filthy","foul","nefarious","vulgar","sorry","unworthy"}; List<string> noun = new List<string>{"villain","blackguard","devil","scoundrel","wretch","cur","rogue"}; //B.Add(TheName(true) + " shouts \"" + verb.Random() + ", " + adjective.Random() + " " + noun.Random() + "!\" "); B.Add("\"" + verb.Random() + ", " + adjective.Random() + " " + noun.Random() + "!\" "); B.Add(the_name + " raises a gauntlet. ",this); B.Add("Sunlight fills the dungeon. "); M.wiz_lite = true; M.wiz_dark = false; Q.Add(new Event((R.Roll(2,20) + 120) * 100,EventType.NORMAL_LIGHTING)); M.Draw(); B.Print(true); attrs[AttrType.COOLDOWN_1]++; foreach(Actor a in M.AllActors()){ if(a != this && a != player && !a.HasAttr(AttrType.BLINDSIGHT) && HasLOS(a)){ a.ApplyStatus(AttrType.BLIND,R.Between(5,9)*100); /*B.Add(a.YouAre() + " blinded! ",a); a.RefreshDuration(AttrType.BLIND,R.Between(5,9)*100,a.YouAre() + " no longer blinded. ",a);*/ } } if(!player.HasAttr(AttrType.BLINDSIGHT) && HasLOS(player)){ //do the player last, so all the previous messages can be seen. player.ApplyStatus(AttrType.BLIND,R.Between(5,9)*100); /*B.Add(player.YouAre() + " blinded! "); player.RefreshDuration(AttrType.BLIND,R.Between(5,9)*100,player.YouAre() + " no longer blinded. ");*/ } } /*if(HasAttr(AttrType.RADIANT_HALO) && !M.wiz_dark && DistanceFrom(dmg.source) <= LightRadius() && HasLOS(dmg.source)){ B.Add(YourVisible() + " radiant halo burns " + dmg.source.TheName(true) + ". ",this,dmg.source); int amount = R.Roll(2,6); if(amount >= dmg.source.curhp){ amount = dmg.source.curhp - 1; } if(dmg.source.curhp > 1){ //this should prevent infinite loops if one haloed entity attacks another dmg.source.TakeDamage(DamageType.MAGIC,DamageClass.MAGICAL,amount,this); } }*/ if(dmg.source == player && IsFinalLevelDemon() && attrs[AttrType.COOLDOWN_2] < 2){ attrs[AttrType.COOLDOWN_2]++; } } } if(HasAttr(AttrType.SPORE_BURST)){ if(type == ActorType.SPORE_POD){ curhp = 0; if(player.CanSee(this)){ B.Add("The spore pod bursts! ",this); } else{ if(DistanceFrom(player) == 1){ B.Add(YouVisible("burst") + "! "); } } List<Tile> area = tile().AddGaseousFeature(FeatureType.SPORES,18); Event.RemoveGas(area,600,FeatureType.SPORES,12); } else{ if(!HasAttr(AttrType.COOLDOWN_1) && dmg.major_damage){ RefreshDuration(AttrType.COOLDOWN_1,50); //cooldown added mostly to prevent several triggers while surrounded by fire. todo: this cooldown is no longer necessary now that searing is minor damage. B.Add(You("retaliate") + " with a burst of spores! ",this); List<Tile> area = tile().AddGaseousFeature(FeatureType.SPORES,8); Event.RemoveGas(area,600,FeatureType.SPORES,12); } } } if(HasFeat(FeatType.BOILING_BLOOD) && dmg.type != DamageType.POISON){ if(attrs[AttrType.BLOOD_BOILED] < 5){ B.Add("Your blood boils! "); if(attrs[AttrType.BLOOD_BOILED] == 4){ RefreshDuration(AttrType.IMMUNE_FIRE,1000); } GainAttrRefreshDuration(AttrType.BLOOD_BOILED,1000,"Your blood cools. "); if(this == player){ Help.TutorialTip(TutorialTopic.IncreasedSpeed); } } else{ RefreshDuration(AttrType.IMMUNE_FIRE,1000); RefreshDuration(AttrType.BLOOD_BOILED,1000,"Your blood cools. "); } } if(type == ActorType.DREAM_SPRITE && (dmg.source != null || (HasLOE(player) && DistanceFrom(player) <= 12))){ attrs[AttrType.COOLDOWN_2] = 1; } if(type == ActorType.MECHANICAL_KNIGHT){ if(old_hp == 5){ curhp = 0; } else{ if(old_hp == 10){ curhp = 5; switch(R.Roll(3)){ case 1: B.Add(Your() + " arms are destroyed! ",this); attrs[AttrType.COOLDOWN_1] = 1; attrs[AttrType.MECHANICAL_SHIELD] = 0; break; case 2: B.Add(Your() + " legs are destroyed! ",this); attrs[AttrType.COOLDOWN_1] = 2; path.Clear(); target_location = null; break; case 3: B.Add(Your() + " head is destroyed! ",this); attrs[AttrType.COOLDOWN_1] = 3; break; } } } } if(dmg.type == DamageType.FIRE && (type == ActorType.TROLL || type == ActorType.TROLL_BLOODWITCH)){ attrs[AttrType.PERMANENT_DAMAGE] += dmg.amount; //permanent damage doesn't regenerate } if(dmg.type == DamageType.FIRE && type == ActorType.SKITTERMOSS && !HasAttr(AttrType.COOLDOWN_1)){ attrs[AttrType.COOLDOWN_1]++; B.Add("The fire kills " + Your() + " insects. ",this); color = Color.White; } if(type == ActorType.ALASI_SCOUT && old_hp == maxhp){ B.Add("The glow leaves " + Your() + " sword. ",this); attrs[AttrType.KEEPS_DISTANCE] = 0; } } if(curhp <= 0){ if(type == ActorType.PLAYER){ if(magic_trinkets.Contains(MagicTrinketType.PENDANT_OF_LIFE)){ curhp = 1; /*attrs[AttrType.INVULNERABLE]++; Q.Add(new Event(this,1,AttrType.INVULNERABLE));*/ if(R.CoinFlip()){ B.Add("Your pendant glows brightly, then crumbles to dust! "); magic_trinkets.Remove(MagicTrinketType.PENDANT_OF_LIFE); } else{ B.Add("Your pendant glows brightly! "); } } else{ if(cause_of_death.Length > 0 && cause_of_death[0] == '*'){ Global.KILLED_BY = cause_of_death.Substring(1); } else{ Global.KILLED_BY = "killed by " + cause_of_death; } M.Draw(); if(Global.GAME_OVER == false){ B.Add("You die. "); } B.PrintAll(); Global.GAME_OVER = true; return false; } } else{ if(HasAttr(AttrType.BOSS_MONSTER)){ M.Draw(); B.Add("The fire drake dies. "); B.PrintAll(); if(player.curhp > 0){ B.Add("The threat to your nation has been slain! You begin the long trek home to deliver the good news... "); Global.KILLED_BY = "Died of ripe old age"; } else{ B.Add("The threat to your nation has been slain! Unfortunately, you won't be able to deliver the news... "); } B.PrintAll(); Global.GAME_OVER = true; Global.BOSS_KILLED = true; } if(dmg.amount < 1000){ //everything that deals this much damage prints its own message if(type == ActorType.BERSERKER){ if(!HasAttr(AttrType.COOLDOWN_1)){ attrs[AttrType.COOLDOWN_1]++; Q.Add(new Event(this,R.Between(3,5)*100,AttrType.COOLDOWN_1)); //changed from 350 Q.KillEvents(this,AttrType.COOLDOWN_2); if(!HasAttr(AttrType.COOLDOWN_2)){ attrs[AttrType.COOLDOWN_2] = DirectionOf(player); } B.Add(the_name + " somehow remains standing! He screams with fury! ",this); } return true; } if(type == ActorType.GHOST){ Event e = Q.FindTargetedEvent(this,EventType.TOMBSTONE_GHOST); if(e != null){ e.dead = true; Q.Add(new Event(null,e.area,R.Between(3,6)*100,EventType.TOMBSTONE_GHOST)); } } if(HasAttr(AttrType.REGENERATES_FROM_DEATH) && dmg.type != DamageType.FIRE){ B.Add(the_name + " collapses, still twitching. ",this); } else{ if(HasAttr(AttrType.REASSEMBLES)){ if(Weapon.IsBlunt(dmg.weapon_used) && R.CoinFlip()){ B.Add(the_name + " is smashed to pieces. ",this); attrs[AttrType.REASSEMBLES] = 0; } else{ B.Add(the_name + " collapses into a pile of bones. ",this); } } else{ if(!HasAttr(AttrType.BOSS_MONSTER) && type != ActorType.SPORE_POD){ if(HasAttr(AttrType.NONLIVING)){ B.Add(the_name + " is destroyed. ",this); } else{ if(type == ActorType.FINAL_LEVEL_CULTIST && dmg.type == DamageType.FIRE){ B.Add(the_name + " is consumed by flames. ",this); List<int> valid_circles = new List<int>(); for(int i=0;i<5;++i){ if(M.FinalLevelSummoningCircle(i).PositionsWithinDistance(2,M.tile).Any(x=>M.tile[x].Is(TileType.DEMONIC_IDOL))){ valid_circles.Add(i); } } int nearest = valid_circles.WhereLeast(x=>DistanceFrom(M.FinalLevelSummoningCircle(x))).Random(); pos circle = M.FinalLevelSummoningCircle(nearest); if(M.actor[circle] != null){ circle = circle.PositionsWithinDistance(3,M.tile).Where(x=>M.tile[x].passable && M.actor[x] == null).Random(); } M.final_level_cultist_count[nearest]++; if(M.final_level_cultist_count[nearest] >= 5){ M.final_level_cultist_count[nearest] = 0; List<ActorType> valid_types = new List<ActorType>{ActorType.MINOR_DEMON}; if(M.final_level_demon_count > 3){ valid_types.Add(ActorType.FROST_DEMON); } if(M.final_level_demon_count > 5){ valid_types.Add(ActorType.BEAST_DEMON); } if(M.final_level_demon_count > 11){ valid_types.Add(ActorType.DEMON_LORD); } if(M.final_level_demon_count > 21){ //eventually, the majority will be demon lords valid_types.Add(ActorType.DEMON_LORD); } if(M.final_level_demon_count > 25){ valid_types.Add(ActorType.DEMON_LORD); } if(M.final_level_demon_count > 31){ valid_types.Add(ActorType.DEMON_LORD); } if(M.final_level_demon_count > 36){ valid_types.Add(ActorType.DEMON_LORD); valid_types.Add(ActorType.DEMON_LORD); valid_types.Add(ActorType.DEMON_LORD); } if(player.CanSee(M.tile[circle])){ B.Add("The flames leap and swirl, and a demon appears! "); } else{ B.Add("You feel an evil presence. "); } Create(valid_types.Random(),circle.row,circle.col); if(M.actor[circle] != null){ M.actor[circle].player_visibility_duration = -1; M.actor[circle].attrs[AttrType.PLAYER_NOTICED] = 1; if(M.actor[circle].type != ActorType.DEMON_LORD){ M.actor[circle].attrs[AttrType.NO_ITEM] = 1; } } M.final_level_demon_count++; } } else{ B.Add(the_name + " dies. ",this); } } } if(IsFinalLevelDemon()){ bool circles = false; bool demons = false; for(int i=0;i<5;++i){ Tile circle = M.tile[M.FinalLevelSummoningCircle(i)]; if(circle.TilesWithinDistance(3).Any(x=>x.type == TileType.DEMONIC_IDOL)){ circles = true; break; } } foreach(Actor a in M.AllActors()){ if(a != this && a.IsFinalLevelDemon()){ demons = true; break; } } if(!circles && !demons){ //victory curhp = 100; B.Add("As the last demon falls, your victory gives you a new surge of strength. "); B.PrintAll(); B.Add("Kersai's summoning has been stopped. His cult will no longer threaten the area. "); B.PrintAll(); B.Add("You begin the journey home to deliver the news. "); B.PrintAll(); Global.GAME_OVER = true; Global.BOSS_KILLED = true; Global.KILLED_BY = "nothing"; return false; } } if(HasAttr(AttrType.REGENERATES_FROM_DEATH) && dmg.type == DamageType.FIRE){ attrs[AttrType.REGENERATES_FROM_DEATH] = 0; } } } } if(HasAttr(AttrType.TURN_INTO_CORPSE)){ attrs[AttrType.CORPSE] = attrs[AttrType.TURN_INTO_CORPSE]; attrs[AttrType.TURN_INTO_CORPSE] = 0; if(!HasAttr(AttrType.NO_CORPSE_KNOCKBACK)){ if(HasAttr(AttrType.NONLIVING)){ SetName("destroyed " + name); } else{ SetName(name + "'s corpse"); } } return false; } if(HasAttr(AttrType.BURNING)){ tile().AddFeature(FeatureType.FIRE); } if(LightRadius() > 0){ UpdateRadius(LightRadius(),0); } if(type == ActorType.SHADOW){ type = ActorType.ZOMBIE; //awful awful hack. (CalculateDimming checks for Shadows) CalculateDimming(); type = ActorType.SHADOW; } if(HasAttr(AttrType.REGENERATES_FROM_DEATH)){ Tile troll = null; foreach(Tile t in M.ReachableTilesByDistance(row,col,false)){ if(!t.Is(TileType.DOOR_O) && !t.Is(FeatureType.TROLL_CORPSE,FeatureType.TROLL_BLOODWITCH_CORPSE,FeatureType.BONES)){ if(type == ActorType.TROLL){ t.AddFeature(FeatureType.TROLL_CORPSE); } else{ t.AddFeature(FeatureType.TROLL_BLOODWITCH_CORPSE); } troll = t; break; } } if(curhp > -3){ curhp = -3; } //curhp -= R.Roll(10)+5; if(curhp < -50){ curhp = -50; } Event e = new Event(troll,100,EventType.REGENERATING_FROM_DEATH); e.value = curhp; e.secondary_value = attrs[AttrType.PERMANENT_DAMAGE]; e.tiebreaker = tiebreakers.IndexOf(this); Q.Add(e); } if(HasAttr(AttrType.REASSEMBLES)){ Tile sk = null; foreach(Tile t in M.ReachableTilesByDistance(row,col,false)){ if(!t.Is(TileType.DOOR_O) && !t.Is(FeatureType.TROLL_CORPSE,FeatureType.TROLL_BLOODWITCH_CORPSE,FeatureType.BONES)){ if(type == ActorType.SKELETON){ t.AddFeature(FeatureType.BONES); } sk = t; break; } } Event e = new Event(sk,R.Between(10,20)*100,EventType.REASSEMBLING); e.tiebreaker = tiebreakers.IndexOf(this); Q.Add(e); } if(type == ActorType.STONE_GOLEM){ List<Tile> deleted = new List<Tile>(); while(true){ bool changed = false; foreach(Tile t in TilesWithinDistance(3)){ if(t.Is(TileType.STALAGMITE) && HasLOE(t)){ t.Toggle(null); deleted.Add(t); changed = true; } } if(!changed){ break; } } Q.RemoveTilesFromEventAreas(deleted,EventType.STALAGMITE); List<Tile> area = new List<Tile>(); foreach(Tile t in TilesWithinDistance(3)){ if((t.IsTrap() || t.Is(TileType.FLOOR,TileType.GRAVE_DIRT,TileType.GRAVEL)) && t.inv == null && (t.actor() == null || t.actor() == this) && HasLOE(t)){ if(R.CoinFlip()){ area.Add(t); } } } if(area.Count > 0){ foreach(Tile t in area){ TileType previous_type = t.type; t.Toggle(null,TileType.STALAGMITE); t.toggles_into = previous_type; } Q.Add(new Event(area,150,EventType.STALAGMITE,5)); } } if(type == ActorType.VULGAR_DEMON && DistanceFrom(player) == 1){ B.Add("The vulgar demon possesses your " + player.EquippedWeapon + "! "); B.Print(true); player.EquippedWeapon.status[EquipmentStatus.POSSESSED] = true; Help.TutorialTip(TutorialTopic.Possessed); } if(type == ActorType.DREAM_SPRITE){ int num = R.Roll(5) + 4; List<Tile> new_area = tile().AddGaseousFeature(FeatureType.PIXIE_DUST,num); if(new_area.Count > 0){ Event.RemoveGas(new_area,400,FeatureType.PIXIE_DUST,25); } } if(type == ActorType.FROSTLING){ if(player.CanSee(tile()) && player.HasLOS(tile())){ AnimateExplosion(this,2,Color.RandomIce,'*'); B.Add("The air freezes around the defeated frostling. ",this); } foreach(Tile t in TilesWithinDistance(2)){ if(HasLOE(t)){ t.ApplyEffect(DamageType.COLD); Actor a = t.actor(); if(a != null && a != this){ a.ApplyFreezing(); } } } } if(player.HasAttr(AttrType.CONVICTION)){ player.attrs[AttrType.KILLSTREAK]++; } if(HasAttr(AttrType.GRABBING)){ Actor grabbed = ActorInDirection(attrs[AttrType.GRABBING]); if(grabbed != null && grabbed.HasAttr(AttrType.GRABBED)){ grabbed.attrs[AttrType.GRABBED]--; } } if(HasAttr(AttrType.HUMANOID_INTELLIGENCE) || type == ActorType.ZOMBIE){ if(R.OneIn(3) && !HasAttr(AttrType.NO_ITEM)){ tile().GetItem(Item.Create(Item.RandomItem(),-1,-1)); } } foreach(Item item in inv){ tile().GetItem(item); } Q.KillEvents(this,EventType.ANY_EVENT); M.RemoveTargets(this); int idx = tiebreakers.IndexOf(this); if(idx != -1){ tiebreakers[idx] = null; } if(group != null){ if(type == ActorType.DREAM_WARRIOR || type == ActorType.DREAM_SPRITE){ List<Actor> temp = new List<Actor>(); foreach(Actor a in group){ if(a != this){ temp.Add(a); a.group = null; } } foreach(Actor a in temp){ a.Kill(); } } else{ if(group.Count >= 2 && this == group[0] && HasAttr(AttrType.WANDERING)){ if(type != ActorType.NECROMANCER){ group[1].attrs[AttrType.WANDERING]++; } } if(group.Count <= 2 || type == ActorType.NECROMANCER){ foreach(Actor a in group){ if(a != this){ a.group = null; } } group.Clear(); group = null; } else{ group.Remove(this); group = null; } } } M.actor[row,col] = null; return false; } } else{ if(HasFeat(FeatType.FEEL_NO_PAIN) && damage_dealt && curhp < 20 && old_hp >= 20){ B.Add("You can feel no pain! "); attrs[AttrType.INVULNERABLE]++; Q.Add(new Event(this,500,AttrType.INVULNERABLE,"You can feel pain again. ")); } if(magic_trinkets.Contains(MagicTrinketType.CLOAK_OF_SAFETY) && damage_dealt && dmg.amount >= curhp){ B.PrintAll(); M.Draw(); if(B.YesOrNoPrompt("Your cloak starts to vanish. Use your cloak to escape?",false)){ PosArray<bool> good = new PosArray<bool>(ROWS,COLS); foreach(Tile t in M.AllTiles()){ if(t.passable){ good[t.row,t.col] = true; } else{ good[t.row,t.col] = false; } } foreach(Actor a in M.AllActors()){ foreach(Tile t in M.AllTiles()){ if(good[t.row,t.col]){ if(a.DistanceFrom(t) < 6 || a.HasLOS(t.row,t.col)){ //was CanSee, but this is safer good[t.row,t.col] = false; } } } } List<Tile> tilelist = new List<Tile>(); Tile destination = null; for(int i=4;i<COLS;++i){ foreach(pos p in PositionsAtDistance(i)){ if(good[p]){ tilelist.Add(M.tile[p.row,p.col]); } } if(tilelist.Count > 0){ destination = tilelist[R.Roll(1,tilelist.Count)-1]; break; } } if(destination != null){ Move(destination.row,destination.col); } else{ for(int i=0;i<9999;++i){ int rr = R.Roll(1,ROWS-2); int rc = R.Roll(1,COLS-2); if(M.tile[rr,rc].passable && M.actor[rr,rc] == null && DistanceFrom(rr,rc) >= 6 && !M.tile[rr,rc].IsTrap()){ Move(rr,rc); break; } } } B.Add("You escape. "); } B.Add("Your cloak vanishes completely! "); magic_trinkets.Remove(MagicTrinketType.CLOAK_OF_SAFETY); } } return true; }
public Tile(Tile t,int r,int c) { type = t.type; name = t.name; a_name = t.a_name; the_name = t.the_name; symbol = t.symbol; color = t.color; if(t.type == TileType.BRUSH){ if(R.CoinFlip()){ color = Color.Yellow; } if(R.OneIn(20)){ color = Color.Green; } } passable = t.passable; opaque = t.opaque; seen = false; revealed_by_light = t.revealed_by_light; solid_rock = false; light_value = 0; toggles_into = t.toggles_into; inv = null; row = r; col = c; light_radius = t.light_radius; direction_exited = 0; sprite_offset = t.sprite_offset; }
public static Tile Create(TileType type,int r,int c) { Tile t = null; if(M.tile[r,c] == null){ t = new Tile(proto[type],r,c); M.tile[r,c] = t; //bounds checking here? } return t; }
public static void Targeting_DisplayContents(Tile tc,string always_displayed,string unseen_area_message,bool include_monsters,bool first_iteration) { if(always_displayed == ""){ if(include_monsters && tc.actor() == player){ if(!first_iteration){ string s = "You're standing here. "; //if(tc.ContentsCount() == 0 && tc.type == TileType.FLOOR){ if(tc.ContentsCount() == 0 && tc.name == "floor"){ B.DisplayNow(s); } else{ B.DisplayNow(s + tc.ContentsString() + " here. "); } } } else{ if(player.CanSee(tc)){ B.DisplayNow(tc.ContentsString(include_monsters) + ". "); if(!Help.displayed[TutorialTopic.Traps] && tc.IsKnownTrap()){ Help.TutorialTip(TutorialTopic.Traps); } else{ if(!Help.displayed[TutorialTopic.NotRevealedByLight] && ((tc.IsShrine() || tc.IsKnownTrap()) && !tc.revealed_by_light) || (tc.inv != null && !tc.inv.revealed_by_light)){ Help.TutorialTip(TutorialTopic.NotRevealedByLight); } else{ if(!Help.displayed[TutorialTopic.Fire] && tc.Is(FeatureType.FIRE)){ Help.TutorialTip(TutorialTopic.Fire); } else{ switch(tc.type){ case TileType.BLAST_FUNGUS: Help.TutorialTip(TutorialTopic.BlastFungus,true); break; case TileType.CRACKED_WALL: Help.TutorialTip(TutorialTopic.CrackedWall,true); break; case TileType.FIREPIT: Help.TutorialTip(TutorialTopic.FirePit,true); break; case TileType.POOL_OF_RESTORATION: Help.TutorialTip(TutorialTopic.PoolOfRestoration,true); break; case TileType.STONE_SLAB: case TileType.STONE_SLAB_OPEN: Help.TutorialTip(TutorialTopic.StoneSlab,true); break; case TileType.COMBAT_SHRINE: case TileType.DEFENSE_SHRINE: case TileType.MAGIC_SHRINE: case TileType.SPIRIT_SHRINE: case TileType.STEALTH_SHRINE: Help.TutorialTip(TutorialTopic.Shrines,true); break; } } } } } else{ if(include_monsters && tc.actor() != null && player.CanSee(tc.actor())){ B.DisplayNow("You sense " + tc.actor().a_name + " " + tc.actor().WoundStatus() + ". "); } else{ if(tc.seen){ if(tc.inv != null){ char itemch = tc.inv.symbol; char screench = Screen.MapChar(tc.row,tc.col).c; if(itemch == screench){ //hacky, but it seems to work (when a monster drops an item you haven't seen yet) if(tc.inv.quantity > 1){ B.DisplayNow("You can no longer see these " + tc.inv.Name(true) + ". "); } else{ B.DisplayNow("You can no longer see this " + tc.inv.Name(true) + ". "); } } else{ B.DisplayNow("You can no longer see this " + tc.Name(true) + ". "); } } else{ B.DisplayNow("You can no longer see this " + tc.Name(true) + ". "); } } else{ B.DisplayNow(unseen_area_message); } } } } } else{ B.DisplayNow(always_displayed); } }
public static void Targeting_ShowLine(Tile tc,int radius,colorchar[,] mem,List<Tile> line,List<Tile> oldline,ref bool blocked,TileDelegate is_blocking) { foreach(Tile t in line){ if(t.row != player.row || t.col != player.col || tc.actor() != player){ colorchar cch = mem[t.row,t.col]; if(t.row == tc.row && t.col == tc.col){ if(!blocked){ cch.bgcolor = Color.Green; if(Global.LINUX && !Screen.GLMode){ //no bright bg in terminals cch.bgcolor = Color.DarkGreen; } if(cch.color == cch.bgcolor){ cch.color = Color.Black; } Screen.WriteMapChar(t.row,t.col,cch); } else{ cch.bgcolor = Color.Red; if(Global.LINUX && !Screen.GLMode){ cch.bgcolor = Color.DarkRed; } if(cch.color == cch.bgcolor){ cch.color = Color.Black; } Screen.WriteMapChar(t.row,t.col,cch); } } else{ if(!blocked){ cch.bgcolor = Color.DarkGreen; if(cch.color == cch.bgcolor){ cch.color = Color.Black; } Screen.WriteMapChar(t.row,t.col,cch); } else{ cch.bgcolor = Color.DarkRed; if(cch.color == cch.bgcolor){ cch.color = Color.Black; } Screen.WriteMapChar(t.row,t.col,cch); } } if(is_blocking(t)){ blocked = true; } } oldline.Remove(t); } if(radius > 0){ foreach(Tile t in tc.TilesWithinDistance(radius,true)){ if(!line.Contains(t)){ colorchar cch = mem[t.row,t.col]; if(blocked){ cch.bgcolor = Color.DarkRed; } else{ cch.bgcolor = Color.DarkGreen; } if(cch.color == cch.bgcolor){ cch.color = Color.Black; } Screen.WriteMapChar(t.row,t.col,cch); oldline.Remove(t); } } } }
public async Task SeekAI() { if (await PathStep()) { return; } switch (atype) { /*case ActorType.SHAMBLING_SCARECROW: if(Global.CoinFlip()){ AI_Step(TileInDirection(Global.RandomDirection())); } else{ if(Global.Roll(1,3) == 3 && DistanceFrom(player) <= 10){ if(player.CanSee(this)){ B.Add(the_name + " emits an eerie whistling sound. "); } else{ B.Add("You hear an eerie whistling sound. "); } } } Q1(); break;*/ case ActorType.BLOOD_MOTH: { PhysicalObject brightest = null; if (!M.wiz_lite) { List<PhysicalObject> current_brightest = new List<PhysicalObject>(); foreach (Tile t in M.AllTiles()) { int pos_radius = t.light_radius; PhysicalObject pos_obj = t; if (t.inv != null && t.inv.light_radius > pos_radius) { pos_radius = t.inv.light_radius; pos_obj = t.inv; } if (t.actor() != null && t.actor().LightRadius() > pos_radius) { pos_radius = t.actor().LightRadius(); pos_obj = t.actor(); } if (pos_radius > 0) { if (current_brightest.Count == 0 && CanSee(t)) { current_brightest.Add(pos_obj); } else { foreach (PhysicalObject o in current_brightest) { if (pos_radius > o.light_radius) { if (CanSee(t)) { current_brightest.Clear(); current_brightest.Add(pos_obj); break; } } else { if (pos_radius == o.light_radius && DistanceFrom(t) < DistanceFrom(o)) { if (CanSee(t)) { current_brightest.Clear(); current_brightest.Add(pos_obj); break; } } else { if (pos_radius == o.light_radius && DistanceFrom(t) == DistanceFrom(o) && pos_obj == player) { if (CanSee(t)) { current_brightest.Clear(); current_brightest.Add(pos_obj); break; } } } } } } } } if (current_brightest.Count > 0) { brightest = current_brightest.Random(); } } if (brightest != null) { if (DistanceFrom(brightest) <= 1) { List<Tile> open = new List<Tile>(); foreach (Tile t in TilesAtDistance(1)) { if (t.DistanceFrom(brightest) <= 1 && t.passable && t.actor() == null) { open.Add(t); } } if (open.Count > 0) { await AI_Step(open.Random()); } QS(); } else { await AI_Step(brightest); QS(); } } else { int dir = Global.RandomDirection(); if (TilesAtDistance(1).Where(t => !t.passable).Count > 4 && !TileInDirection(dir).passable) { dir = Global.RandomDirection(); } if (TileInDirection(dir).passable && ActorInDirection(dir) == null) { await AI_Step(TileInDirection(dir)); QS(); } else { if (player.HasLOS(TileInDirection(dir))) { if (!TileInDirection(dir).passable) { B.Add(the_name + " brushes up against " + TileInDirection(dir).the_name + ". ", this); } else { if (ActorInDirection(dir) != null) { B.Add(the_name + " brushes up against " + ActorInDirection(dir).TheVisible() + ". ", this); } } } QS(); } } break; } case ActorType.PHASE_SPIDER: if (DistanceFrom(target) <= 10) { if (Global.Roll(1, 4) == 4) { //teleport into target's LOS somewhere nearby List<Tile> tilelist = new List<Tile>(); for (int i = 0; i < ROWS; ++i) { for (int j = 0; j < COLS; ++j) { if (M.tile[i, j].passable && M.actor[i, j] == null) { if (DistanceFrom(i, j) <= 10 && target.DistanceFrom(i, j) <= 10 && target.CanSee(i, j)) { tilelist.Add(M.tile[i, j]); } } } } if (tilelist.Count > 0) { Tile t = tilelist[Global.Roll(1, tilelist.Count) - 1]; await Move(t.row, t.col); } QS(); } else { //do nothing QS(); } } else { //forget about target, do nothing target = null; QS(); } break; case ActorType.ORC_WARMAGE: foreach (Actor a in ActorsWithinDistance(2)) { if (a.HasAttr(AttrType.SPELL_DISRUPTION) && a.HasLOE(this)) { QS(); return; } } if (!HasAttr(AttrType.BLOODSCENT)) { await CastSpell(SpellType.BLOODSCENT); } else { QS(); } break; case ActorType.CARNIVOROUS_BRAMBLE: case ActorType.MUD_TENTACLE: case ActorType.LASHER_FUNGUS: case ActorType.MARBLE_HORROR_STATUE: QS(); break; case ActorType.FIRE_DRAKE: FindPath(player); QS(); break; default: if (target_location != null) { if (DistanceFrom(target_location) == 1 && M.actor[target_location.p] != null) { if (GrabPreventsMovement(target_location) || M.actor[target_location.p].GrabPreventsMovement(tile()) || HasAttr(AttrType.NEVER_MOVES) || M.actor[target_location.p].HasAttr(AttrType.NEVER_MOVES)) { QS(); //todo: should target_location be cleared here? } else { await Move(target_location.row, target_location.col); //swap places target_location = null; QS(); } } else { if (await AI_Step(target_location)) { QS(); if (DistanceFrom(target_location) == 0) { target_location = null; } } else { //could not move, end turn. if (DistanceFrom(target_location) == 1 && !target_location.passable) { target_location = null; } QS(); } } } else { if (DistanceFrom(target) <= 5) { if (DistanceFrom(target) <= 3) { List<pos> path2 = GetPath(target, 4); if (path2.Count > 0) { path = path2; player_visibility_duration = -1; //stay at -1 while in close pursuit } } else { List<pos> path2 = GetPath(target, 8); if (path2.Count <= 10) { path = path2; } } //FindPath(target,8); if (await PathStep()) { return; } QS(); } else { //if they're too far away, forget them and end turn. target = null; if (group != null && group[0] != this) { if (DistanceFrom(group[0]) > 1) { int dir = DirectionOf(group[0]); bool found = false; for (int i = -1; i <= 1; ++i) { Actor a = ActorInDirection(RotateDirection(dir, true, i)); if (a != null && group.Contains(a)) { found = true; break; } } if (!found) { if (HasLOS(group[0])) { await AI_Step(group[0]); } else { FindPath(group[0], 8); if (await PathStep()) { return; } } } } } QS(); } } break; } }
public async Task<bool> TakeDamage(Damage dmg, string cause_of_death) { //returns true if still alive bool damage_dealt = false; int old_hp = curhp; if (HasAttr(AttrType.FROZEN)) { //attrs[Forays.AttrType.FROZEN] -= (dmg.amount+1) / 2; attrs[Forays.AttrType.FROZEN] -= (dmg.amount * 9) / 10; if (attrs[Forays.AttrType.FROZEN] <= 0) { attrs[Forays.AttrType.FROZEN] = 0; B.Add("The ice breaks! ", this); } //dmg.amount = dmg.amount / 2; dmg.amount = dmg.amount / 10; } if (HasAttr(AttrType.MECHANICAL_SHIELD)) { B.Add(Your() + " shield moves to protect it from harm. ", this); return true; } if (HasAttr(AttrType.INVULNERABLE)) { dmg.amount = 0; } if (HasAttr(AttrType.TOUGH) && dmg.damclass == DamageClass.PHYSICAL) { dmg.amount -= 2; } if (dmg.damclass == DamageClass.MAGICAL) { dmg.amount -= TotalSkill(SkillType.SPIRIT) / 2; } if (HasAttr(AttrType.ARCANE_SHIELDED)) { if (attrs[Forays.AttrType.ARCANE_SHIELDED] >= dmg.amount) { attrs[Forays.AttrType.ARCANE_SHIELDED] -= dmg.amount; if (attrs[Forays.AttrType.ARCANE_SHIELDED] < 0) { attrs[Forays.AttrType.ARCANE_SHIELDED] = 0; } dmg.amount = 0; } else { dmg.amount -= attrs[Forays.AttrType.ARCANE_SHIELDED]; attrs[Forays.AttrType.ARCANE_SHIELDED] = 0; } if (!HasAttr(AttrType.ARCANE_SHIELDED)) { B.Add(Your() + " arcane shield crumbles. ", this); } } bool resisted = false; switch (dmg.type) { case DamageType.NORMAL: if (dmg.amount > 0) { curhp -= dmg.amount; damage_dealt = true; } else { B.Add(YouAre() + " undamaged. ", this); } break; case DamageType.SLASHING: { int div = 1; if (HasAttr(AttrType.RESIST_SLASH)) { for (int i = attrs[AttrType.RESIST_SLASH]; i > 0; --i) { div = div * 2; } B.Add(You("resist") + ". ", this); resisted = true; } dmg.amount = dmg.amount / div; if (dmg.amount > 0) { curhp -= dmg.amount; damage_dealt = true; } else { B.Add(YouAre() + " unharmed. ", this); } break; } case DamageType.BASHING: { int div = 1; if (HasAttr(AttrType.RESIST_BASH)) { for (int i = attrs[AttrType.RESIST_BASH]; i > 0; --i) { div = div * 2; } B.Add(You("resist") + ". ", this); resisted = true; } dmg.amount = dmg.amount / div; if (dmg.amount > 0) { curhp -= dmg.amount; damage_dealt = true; } else { B.Add(YouAre() + " unharmed. ", this); } break; } case DamageType.PIERCING: { int div = 1; if (HasAttr(AttrType.RESIST_PIERCE)) { for (int i = attrs[AttrType.RESIST_PIERCE]; i > 0; --i) { div = div * 2; } B.Add(You("resist") + ". ", this); resisted = true; } dmg.amount = dmg.amount / div; if (dmg.amount > 0) { curhp -= dmg.amount; damage_dealt = true; } else { B.Add(YouAre() + " unharmed. ", this); } break; } case DamageType.MAGIC: if (dmg.amount > 0) { curhp -= dmg.amount; damage_dealt = true; } else { B.Add(YouAre() + " unharmed. ", this); } break; case DamageType.FIRE: { int div = 1; if (HasAttr(AttrType.IMMUNE_FIRE)) { dmg.amount = 0; //B.Add(the_name + " is immune! ",this); } else { if (HasAttr(AttrType.RESIST_FIRE)) { for (int i = attrs[AttrType.RESIST_FIRE]; i > 0; --i) { div = div * 2; } B.Add(You("resist") + ". ", this); resisted = true; } } dmg.amount = dmg.amount / div; if (dmg.amount > 0) { curhp -= dmg.amount; damage_dealt = true; /*if(type == ActorType.SHAMBLING_SCARECROW && speed != 50){ speed = 50; if(attrs[AttrType.ON_FIRE] >= LightRadius()){ UpdateRadius(LightRadius(),LightRadius()+1); } attrs[AttrType.ON_FIRE]++; B.Add(the_name + " leaps about as it catches fire! ",this); }*/ } else { if (atype != ActorType.CORPSETOWER_BEHEMOTH) { B.Add(YouAre() + " unburnt. ", this); } } break; } case DamageType.COLD: { int div = 1; if (HasAttr(AttrType.IMMUNE_COLD)) { dmg.amount = 0; //B.Add(YouAre() + " unharmed. ",this); } else { if (HasAttr(AttrType.RESIST_COLD)) { for (int i = attrs[AttrType.RESIST_COLD]; i > 0; --i) { div = div * 2; } B.Add(You("resist") + ". ", this); resisted = true; } } dmg.amount = dmg.amount / div; if (dmg.amount > 0) { curhp -= dmg.amount; damage_dealt = true; } else { B.Add(YouAre() + " unharmed. ", this); } break; } case DamageType.ELECTRIC: { int div = 1; if (HasAttr(AttrType.RESIST_ELECTRICITY)) { for (int i = attrs[AttrType.RESIST_ELECTRICITY]; i > 0; --i) { div = div * 2; } B.Add(You("resist") + ". ", this); resisted = true; } dmg.amount = dmg.amount / div; if (dmg.amount > 0) { curhp -= dmg.amount; damage_dealt = true; } else { B.Add(YouAre() + " unharmed. ", this); } break; } case DamageType.POISON: if (HasAttr(AttrType.UNDEAD) || HasAttr(AttrType.CONSTRUCT) || HasAttr(AttrType.IMMUNE_TOXINS)) { dmg.amount = 0; } if (dmg.amount > 0) { curhp -= dmg.amount; damage_dealt = true; if (atype == ActorType.PLAYER) { if (tile().Is(FeatureType.POISON_GAS)) { B.Add("The poisonous gas burns your skin! "); } else { B.Add("You feel the poison coursing through your veins! "); } } else { if (Global.Roll(1, 5) == 5) { B.Add(the_name + " shudders. ", this); } } } break; case DamageType.HEAL: curhp += dmg.amount; if (curhp > maxhp) { curhp = maxhp; } break; case DamageType.NONE: break; } if (dmg.source != null && dmg.source == player && dmg.damclass == DamageClass.PHYSICAL && resisted && !(cause_of_death.Search(new Regex("arrow")) > -1)) { await Help.TutorialTip(TutorialTopic.Resistance); } if (damage_dealt) { if (HasAttr(AttrType.MAGICAL_BLOOD)) { recover_time = Q.turn + 200; } else { recover_time = Q.turn + 500; } Interrupt(); if (HasAttr(AttrType.ASLEEP)) { attrs[Forays.AttrType.ASLEEP] = 0; Global.FlushInput(); } if (dmg.source != null) { if (atype != ActorType.PLAYER && dmg.source != this) { target = dmg.source; target_location = M.tile[dmg.source.row, dmg.source.col]; if (dmg.source.IsHiddenFrom(this)) { player_visibility_duration = -1; } if (atype == ActorType.CRUSADING_KNIGHT && dmg.source == player && !HasAttr(AttrType.COOLDOWN_1) && !M.wiz_lite && !CanSee(player) && curhp > 0) { List<string> verb = new List<string> { "Show yourself", "Reveal yourself", "Unfold thyself", "Present yourself", "Unveil yourself", "Make yourself known" }; List<string> adjective = new List<string> { "despicable", "filthy", "foul", "nefarious", "vulgar", "sorry", "unworthy" }; List<string> noun = new List<string> { "villain", "blackguard", "devil", "scoundrel", "wretch", "cur", "rogue" }; B.Add(TheVisible() + " shouts \"" + verb.Random() + ", " + adjective.Random() + " " + noun.Random() + "!\" "); B.Add(the_name + " raises a gauntlet. ", this); B.Add("Sunlight fills the dungeon. "); M.wiz_lite = true; M.wiz_dark = false; attrs[Forays.AttrType.COOLDOWN_1]++; } } } if (HasAttr(AttrType.SPORE_BURST) && !HasAttr(AttrType.COOLDOWN_1)) { attrs[AttrType.COOLDOWN_1]++; Q.Add(new Event(this, (Global.Roll(1, 5) + 1) * 100, AttrType.COOLDOWN_1)); B.Add(You("retaliate") + " with a burst of spores! ", this); for (int i = 2; i <= 8; i += 2) { AnimateStorm(i, 1, (((i * 2) + 1) * ((i * 2) + 1)) / 4, "*", Color.DarkYellow); } foreach (Actor a in ActorsWithinDistance(8)) { if (HasLOE(a.row, a.col) && a != this) { B.Add("The spores hit " + a.the_name + ". ", a); if (!a.HasAttr(AttrType.UNDEAD) && !a.HasAttr(AttrType.CONSTRUCT) && !a.HasAttr(AttrType.SPORE_BURST) && !a.HasAttr(AttrType.IMMUNE_TOXINS)) { int duration = Global.Roll(2, 4); a.attrs[AttrType.POISONED]++; Q.Add(new Event(a, duration * 100, AttrType.POISONED)); if (a.name == "you") { B.Add("You are poisoned. "); } if (!a.HasAttr(AttrType.STUNNED)) { a.attrs[AttrType.STUNNED]++; Q.Add(new Event(a, duration * 100, AttrType.STUNNED, a.YouAre() + " no longer stunned. ", new PhysicalObject[] { a })); B.Add(a.YouAre() + " stunned. ", a); } } else { B.Add(a.YouAre() + " unaffected. ", a); } } } } if (HasAttr(AttrType.HOLY_SHIELDED) && dmg.source != null) { B.Add(YourVisible() + " holy shield burns " + dmg.source.TheVisible() + ". ", new PhysicalObject[] { this, dmg.source }); int amount = Global.Roll(2, 6); if (amount >= dmg.source.curhp) { amount = dmg.source.curhp - 1; } await dmg.source.TakeDamage(DamageType.MAGIC, DamageClass.MAGICAL, amount, this); //doesn't yet prevent loops involving 2 holy shields. } if (HasFeat(FeatType.BOILING_BLOOD) && dmg.type != DamageType.POISON && attrs[AttrType.BLOOD_BOILED] < 5) { //if(!Global.Option(OptionType.NO_BLOOD_BOIL_MESSAGE)){ B.Add("Your blood boils! "); //} speed -= 10; attrs[AttrType.BLOOD_BOILED]++; Q.KillEvents(this, AttrType.BLOOD_BOILED); //eventually replace this with refreshduration //GainAttr(AttrType.BLOOD_BOILED,1001,attrs[Forays.AttrType.BLOOD_BOILED],"Your blood cools. "); Q.Add(new Event(this, 1000, Forays.AttrType.BLOOD_BOILED, attrs[Forays.AttrType.BLOOD_BOILED], "Your blood cools. ")); } if (atype == ActorType.MECHANICAL_KNIGHT) { if (curhp <= 10 && curhp > 0 && !HasAttr(AttrType.COOLDOWN_1) && !HasAttr(AttrType.COOLDOWN_2)) { if (Global.CoinFlip()) { B.Add(Your() + " arms are destroyed! ", this); attrs[Forays.AttrType.COOLDOWN_1]++; attrs[Forays.AttrType.MECHANICAL_SHIELD] = 0; } else { B.Add(Your() + " legs are destroyed! ", this); attrs[Forays.AttrType.COOLDOWN_2]++; attrs[Forays.AttrType.NEVER_MOVES]++; path.Clear(); target_location = null; } } } } if (curhp <= 0) { if (atype == ActorType.PLAYER) { if (magic_items.Contains(MagicItemType.PENDANT_OF_LIFE)) { magic_items.Remove(MagicItemType.PENDANT_OF_LIFE); curhp = 1; B.Add("Your pendant glows brightly, then crumbles to dust. "); } else { if (cause_of_death.Length > 0 && cause_of_death[0] == '*') { Global.KILLED_BY = cause_of_death.Substring(1); } else { Global.KILLED_BY = "killed by " + cause_of_death; } M.Draw(); if (Global.GAME_OVER == false) { B.Add("You die. "); } await B.PrintAll(); Global.GAME_OVER = true; return false; } } else { if (HasAttr(AttrType.BOSS_MONSTER)) { M.Draw(); B.Add("The fire drake dies. "); await B.PrintAll(); if (player.curhp > 0) { B.Add("The threat to your nation has been slain! You begin the long trek home to deliver the good news... "); Global.KILLED_BY = "Died of ripe old age"; } else { B.Add("The threat to your nation has been slain! Unfortunately, you won't be able to deliver the news... "); } await B.PrintAll(); Global.GAME_OVER = true; Global.BOSS_KILLED = true; } if (atype == ActorType.BERSERKER && dmg.amount < 1000) { //hack if (!HasAttr(AttrType.COOLDOWN_1)) { attrs[AttrType.COOLDOWN_1]++; Q.Add(new Event(this, 350, AttrType.COOLDOWN_1)); Q.KillEvents(this, AttrType.COOLDOWN_2); if (!HasAttr(AttrType.COOLDOWN_2)) { attrs[AttrType.COOLDOWN_2] = DirectionOf(player); } B.Add(the_name + " somehow remains standing! He screams with fury! ", this); } return true; } if (HasAttr(AttrType.REGENERATES_FROM_DEATH) && dmg.type != DamageType.FIRE) { B.Add(the_name + " falls to the ground, still twitching. ", this); Tile troll = null; for (int i = 0; i < COLS && troll == null; ++i) { foreach (Tile t in TilesAtDistance(i)) { if (t.passable && !t.Is(FeatureType.TROLL_CORPSE) && !t.Is(FeatureType.TROLL_SEER_CORPSE) && !t.Is(FeatureType.QUICKFIRE)) { if (atype == ActorType.TROLL) { t.features.Add(FeatureType.TROLL_CORPSE); } else { t.features.Add(FeatureType.TROLL_SEER_CORPSE); } troll = t; break; } } } curhp -= Global.Roll(10) + 5; if (curhp < -50) { curhp = -50; } AttrType attr = HasAttr(AttrType.COOLDOWN_1) ? AttrType.COOLDOWN_1 : AttrType.NO_ATTR; Q.Add(new Event(troll, null, 200, EventType.REGENERATING_FROM_DEATH, attr, curhp, "")); } else { if (dmg.amount < 1000 && !HasAttr(AttrType.BOSS_MONSTER)) { //everything that deals this much damage if (HasAttr(AttrType.UNDEAD) || HasAttr(AttrType.CONSTRUCT)) { //prints its own message B.Add(the_name + " is destroyed. ", this); } else { B.Add(the_name + " dies. ", this); } } } if (LightRadius() > 0) { UpdateRadius(LightRadius(), 0); } if (atype == ActorType.SHADOW) { if (player.HasAttr(AttrType.DIM_LIGHT)) { atype = ActorType.ZOMBIE; //awful awful hack. (CalculateDimming checks for Shadows) CalculateDimming(); } } if (atype == ActorType.STONE_GOLEM) { foreach (Tile t in TilesWithinDistance(4)) { if (t.name == "floor" && (t.actor() == null || t.actor() == this) && HasLOE(t)) { if (DistanceFrom(t) <= 2 || Global.CoinFlip()) { t.TransformTo(TileType.RUBBLE); } } } } if (player.HasAttr(AttrType.CONVICTION)) { player.attrs[Forays.AttrType.KILLSTREAK]++; } if ((HasAttr(AttrType.HUMANOID_INTELLIGENCE) && atype != ActorType.DREAM_CLONE && atype != ActorType.FIRE_DRAKE) || atype == ActorType.ZOMBIE) { if (Global.CoinFlip() && !HasAttr(AttrType.NO_ITEM)) { tile().GetItem(Item.Create(Item.RandomItem(), -1, -1)); } } foreach (Item item in inv) { tile().GetItem(item); } /*int divisor = 1; if(HasAttr(AttrType.SMALL_GROUP)){ divisor = 2; } if(HasAttr(AttrType.MEDIUM_GROUP)){ divisor = 3; } if(HasAttr(AttrType.LARGE_GROUP)){ divisor = 5; } if(!Global.GAME_OVER){ player.GainXP(xp + (level*(10 + level - player.level))/divisor); //experimentally giving the player any }*/ Q.KillEvents(this, EventType.ANY_EVENT); // XP that the monster had collected. currently always 0. M.RemoveTargets(this); int idx = Actor.tiebreakers.IndexOf(this); if (idx != -1) { Actor.tiebreakers[Actor.tiebreakers.IndexOf(this)] = null; } if (group != null) { if (group.Count >= 2 && this == group[0] && HasAttr(AttrType.WANDERING)) { if (atype != ActorType.NECROMANCER && atype != ActorType.DREAM_WARRIOR) { group[1].attrs[Forays.AttrType.WANDERING]++; } } if (group.Count <= 2 || atype == ActorType.NECROMANCER || atype == ActorType.DREAM_WARRIOR) { foreach (Actor a in group) { if (a != this) { a.group = null; } } group.Clear(); group = null; } else { group.Remove(this); group = null; } } M.actor[row, col] = null; return false; } } else { if (HasFeat(FeatType.FEEL_NO_PAIN) && damage_dealt && curhp < 20 && old_hp >= 20) { B.Add("You can feel no pain! "); attrs[AttrType.INVULNERABLE]++; Q.Add(new Event(this, 500, AttrType.INVULNERABLE, "You can feel pain again. ")); } if (magic_items.Contains(MagicItemType.CLOAK_OF_DISAPPEARANCE) && damage_dealt && dmg.amount >= curhp) { await B.PrintAll(); M.Draw(); B.DisplayNow("Your cloak starts to vanish. Use your cloak to escape?(y/n): "); Game.Console.CursorVisible = true; ConsoleKeyInfo command; bool done = false; while (!done) { command = await Game.Console.ReadKey(true); switch (command.KeyChar) { case 'n': case 'N': done = true; break; case 'y': case 'Y': done = true; bool[,] good = new bool[ROWS, COLS]; foreach (Tile t in M.AllTiles()) { if (t.passable) { good[t.row, t.col] = true; } else { good[t.row, t.col] = false; } } foreach (Actor a in M.AllActors()) { foreach (Tile t in M.AllTiles()) { if (good[t.row, t.col]) { if (a.DistanceFrom(t) < 6 || a.HasLOS(t.row, t.col)) { //was CanSee, but this is safer good[t.row, t.col] = false; } } } } List<Tile> tilelist = new List<Tile>(); Tile destination = null; for (int i = 4; i < COLS; ++i) { foreach (pos p in PositionsAtDistance(i)) { if (good[p.row, p.col]) { tilelist.Add(M.tile[p.row, p.col]); } } if (tilelist.Count > 0) { destination = tilelist[Global.Roll(1, tilelist.Count) - 1]; break; } } if (destination != null) { await Move(destination.row, destination.col); } else { for (int i = 0; i < 9999; ++i) { int rr = Global.Roll(1, ROWS - 2); int rc = Global.Roll(1, COLS - 2); if (M.tile[rr, rc].passable && M.actor[rr, rc] == null && DistanceFrom(rr, rc) >= 6 && !M.tile[rr, rc].IsTrap()) { await Move(rr, rc); break; } } } B.Add("You escape. "); break; default: break; } } B.Add("Your cloak vanishes completely! "); magic_items.Remove(MagicItemType.CLOAK_OF_DISAPPEARANCE); } } return true; }
public Actor(ActorType type_,string name_,char symbol_,Color color_,int maxhp_,int speed_,int light_radius_,params AttrType[] attrlist) { type = type_; SetName(name_); symbol = symbol_; color = color_; maxhp = maxhp_; curhp = maxhp; maxmp = 0; curmp = maxmp; speed = speed_; light_radius = light_radius_; target = null; inv = null; target_location = null; time_of_last_action = 0; recover_time = 0; player_visibility_duration = 0; exhaustion = 0; foreach(AttrType at in attrlist){ attrs[at]++; }//row and col are -1 switch(type){ case ActorType.PLAYER: sprite_offset = new pos(0,32); break; case ActorType.MUD_TENTACLE: sprite_offset = new pos(13,32); break; case ActorType.GHOST: sprite_offset = new pos(13,34); break; default: if(type >= ActorType.MINOR_DEMON && type <= ActorType.DEMON_LORD){ int diff = type - ActorType.MINOR_DEMON; sprite_offset = new pos(13,36+diff); } else{ //phantoms are handled in CreatePhantom() int diff = type - ActorType.GOBLIN; sprite_offset = new pos(4 + diff / 8,32 + (diff % 8)*2); } break; } }
public bool Attack(int attack_idx,Actor a,bool attack_is_part_of_another_action) { //returns true if attack hit AttackInfo info = attack[type][attack_idx]; pos original_pos = p; pos target_original_pos = a.p; if(EquippedWeapon.type != WeaponType.NO_WEAPON){ info = EquippedWeapon.Attack(); } info.damage.source = this; if(a.HasFeat(FeatType.DEFLECT_ATTACK) && DistanceFrom(a) == 1){ //Actor other = a.ActorsWithinDistance(1).Where(x=>x.DistanceFrom(this) == 1).Random(); Actor other = a.ActorsWithinDistance(1).Where(x=>x != this).RandomOrDefault(); if(other != a){ B.Add(a.You("deflect") + "! ",this,a); return Attack(attack_idx,other,attack_is_part_of_another_action); } } if(!attack_is_part_of_another_action && StunnedThisTurn()){ return false; } if(!attack_is_part_of_another_action && exhaustion == 100 && R.CoinFlip()){ B.Add(You("fumble") + " from exhaustion. ",this); Q1(); //this is checked in PlayerWalk if attack_is_part_of_another_action is true return false; } if(!attack_is_part_of_another_action && this == player && EquippedWeapon.status[EquipmentStatus.POSSESSED] && R.CoinFlip()){ List<Actor> actors = ActorsWithinDistance(1); Actor chosen = actors.RandomOrDefault(); if(chosen != a){ if(chosen == this){ B.Add("Your possessed " + EquippedWeapon.NameWithEnchantment() + " tries to attack you! "); B.Add("You fight it off! "); //this is also checked in PlayerWalk if attack_is_part_of_another_action is true Q1(); return false; } else{ return Attack(attack_idx,chosen); } } } bool player_in_combat = false; if(this == player || a == player){ player_in_combat = true; } /*if(a == player && (type == ActorType.DREAM_WARRIOR_CLONE || type == ActorType.DREAM_SPRITE_CLONE)){ player_in_combat = false; }*/ if(player_in_combat){ player.attrs[AttrType.IN_COMBAT]++; } if(a.HasAttr(AttrType.CAN_DODGE) && a.CanSee(this)){ int dodge_dir = R.Roll(9); Tile dodge_tile = a.TileInDirection(dodge_dir); bool failed_to_dodge = false; if(HasAttr(AttrType.CONFUSED,AttrType.SLOWED,AttrType.STUNNED) && R.CoinFlip()){ failed_to_dodge = true; } if(a.tile().Is(FeatureType.WEB) && !a.HasAttr(AttrType.BURNING,AttrType.OIL_COVERED,AttrType.SLIMED,AttrType.BRUTISH_STRENGTH)){ failed_to_dodge = true; } if(!failed_to_dodge && dodge_tile.passable && dodge_tile.actor() == null && !a.MovementPrevented(dodge_tile) && !a.HasAttr(AttrType.PARALYZED)){ B.Add(a.You("dodge") + " " + YourVisible() + " attack. ",this,a); if(player.CanSee(a)){ Help.TutorialTip(TutorialTopic.Dodging); } if(a == player){ B.DisplayNow(); Screen.AnimateMapCell(a.row,a.col,new colorchar('!',Color.Green),80); } a.Move(dodge_tile.row,dodge_tile.col); if(a != player && DistanceFrom(dodge_tile) > 1){ M.Draw(); Thread.Sleep(40); } if(!attack_is_part_of_another_action){ Q.Add(new Event(this,info.cost)); } return false; } } if(a.HasFeat(FeatType.CUNNING_DODGE) && !this.HasAttr(AttrType.DODGED)){ attrs[AttrType.DODGED]++; B.Add(a.You("dodge") + " " + YourVisible() + " attack. ",this,a); if(!attack_is_part_of_another_action){ Q.Add(new Event(this,info.cost)); } return false; } if(IsInvisibleHere() || a.IsInvisibleHere()){ Help.TutorialTip(TutorialTopic.FightingTheUnseen); } //pos pos_of_target = new pos(a.row,a.col); bool a_moved_last_turn = !a.HasAttr(AttrType.TURNS_HERE); bool drive_back_applied = HasFeat(FeatType.DRIVE_BACK); if(drive_back_applied && !ConfirmsSafetyPrompts(a.tile())){ drive_back_applied = false; } bool drive_back_nowhere_to_run = false; if(!attack_is_part_of_another_action && drive_back_applied){ //doesn't work while moving drive_back_nowhere_to_run = true; int dir = DirectionOf(a); foreach(int next_dir in new List<int>{dir,dir.RotateDir(true),dir.RotateDir(false)}){ Tile t = a.TileInDirection(next_dir); if(t.passable && t.actor() == null && !a.MovementPrevented(t)){ drive_back_nowhere_to_run = false; break; } } /*if(a.TileInDirection(dir).passable && a.ActorInDirection(dir) == null && !a.GrabPreventsMovement(TileInDirection(dir))){ drive_back_nowhere_to_run = false; } if(a.TileInDirection(dir.RotateDir(true)).passable && a.ActorInDirection(dir.RotateDir(true)) == null && !a.GrabPreventsMovement(TileInDirection(dir.RotateDir(true)))){ drive_back_nowhere_to_run = false; } if(a.TileInDirection(dir.RotateDir(false)).passable && a.ActorInDirection(dir.RotateDir(false)) == null && !a.GrabPreventsMovement(TileInDirection(dir.RotateDir(false)))){ drive_back_nowhere_to_run = false; }*/ if(a.tile().IsSlippery() && !(a.tile().Is(TileType.ICE) && a.type == ActorType.FROSTLING)){ if(R.OneIn(5) && !HasAttr(AttrType.FLYING,AttrType.NONEUCLIDEAN_MOVEMENT) && !Is(ActorType.GIANT_SLUG,ActorType.MACHINE_OF_WAR,ActorType.MUD_ELEMENTAL)){ drive_back_nowhere_to_run = true; } } if(a.HasAttr(AttrType.FROZEN) || a.HasAttr(AttrType.IMMOBILE)){ drive_back_nowhere_to_run = true; //todo: exception for noneuclidean monsters? i think they'll just move out of the way. } } bool obscured_vision_miss = false; { bool fog = false; bool hidden = false; if((this.tile().Is(FeatureType.FOG,FeatureType.THICK_DUST) || a.tile().Is(FeatureType.FOG,FeatureType.THICK_DUST))){ fog = true; } if(a.IsHiddenFrom(this) || !CanSee(a) || (a.IsInvisibleHere() && !HasAttr(AttrType.BLINDSIGHT))){ hidden = true; } if(!HasAttr(AttrType.DETECTING_MONSTERS) && (fog || hidden) && R.CoinFlip()){ obscured_vision_miss = true; } } int plus_to_hit = TotalSkill(SkillType.COMBAT); bool sneak_attack = false; if(this.IsHiddenFrom(a) || !a.CanSee(this) || (this == player && IsInvisibleHere() && !a.HasAttr(AttrType.BLINDSIGHT))){ sneak_attack = true; a.attrs[AttrType.SEES_ADJACENT_PLAYER] = 1; if(DistanceFrom(a) > 2 && this != player){ sneak_attack = false; //no phantom blade sneak attacks from outside your view - but the player can sneak attack at this range with a wand of reach. } } //...insert any other changes to sneak attack calculation here... if(sneak_attack || HasAttr(AttrType.LUNGING_AUTO_HIT) || (EquippedWeapon == Dagger && !tile().IsLit()) || (EquippedWeapon == Staff && a_moved_last_turn) || a.HasAttr(AttrType.SWITCHING_ARMOR)){ //some attacks get +25% accuracy. this usually totals 100% vs. unarmored targets. plus_to_hit += 25; } plus_to_hit -= a.TotalSkill(SkillType.DEFENSE) * 3; bool attack_roll_hit = a.IsHit(plus_to_hit); bool blocked_by_armor_miss = false; bool blocked_by_root_shell_miss = false; bool mace_through_armor = false; if(!attack_roll_hit){ int armor_value = a.TotalProtectionFromArmor(); if(a != player){ armor_value = a.TotalSkill(SkillType.DEFENSE); //if monsters have Defense skill, it's from armor } int roll = R.Roll(25 - plus_to_hit); if(roll <= armor_value * 3){ bool mace = (EquippedWeapon == Mace || type == ActorType.CRUSADING_KNIGHT || type == ActorType.PHANTOM_CRUSADER); if(mace){ attack_roll_hit = true; mace_through_armor = true; } else{ if(type == ActorType.CORROSIVE_OOZE || type == ActorType.LASHER_FUNGUS){ //this is a bit hacky, but these are the only ones that aren't stopped by armor right now. attack_roll_hit = true; } else{ blocked_by_armor_miss = true; } } } else{ if(a.HasAttr(AttrType.ROOTS) && roll <= (armor_value + 10) * 3){ //potion of roots gives 10 defense blocked_by_root_shell_miss = true; } } } bool hit = true; if(obscured_vision_miss){ //this calculation turned out to be pretty complicated hit = false; } else{ if(blocked_by_armor_miss || blocked_by_root_shell_miss){ hit = false; } else{ if(drive_back_nowhere_to_run || attack_roll_hit){ hit = true; } else{ hit = false; } } } if(a.HasAttr(AttrType.GRABBED) && attrs[AttrType.GRABBING] == DirectionOf(a)){ hit = true; //one more modifier: automatically hit things you're grabbing. } bool weapon_just_poisoned = false; if(!hit){ if(blocked_by_armor_miss){ bool initial_message_printed = false; //for better pronoun usage if(info.blocked != ""){ initial_message_printed = true; string s = info.blocked + ". "; int pos = -1; do{ pos = s.IndexOf('&'); if(pos != -1){ s = s.Substring(0,pos) + TheName(true) + s.Substring(pos+1); } } while(pos != -1); // do{ pos = s.IndexOf('*'); if(pos != -1){ s = s.Substring(0,pos) + a.TheName(true) + s.Substring(pos+1); } } while(pos != -1); B.Add(s,this,a); } if(a.HasFeat(FeatType.ARMOR_MASTERY) && !(a.type == ActorType.ALASI_SCOUT && attack_idx == 1)){ B.Add(a.YourVisible() + " armor blocks the attack, leaving " + TheName(true) + " off-balance. ",a,this); RefreshDuration(AttrType.SUSCEPTIBLE_TO_CRITS,100); } else{ if(initial_message_printed){ B.Add(a.YourVisible() + " armor blocks the attack. ",this,a); } else{ B.Add(a.YourVisible() + " armor blocks " + YourVisible() + " attack. ",this,a); } } if(a.EquippedArmor.type == ArmorType.FULL_PLATE && !HasAttr(AttrType.BRUTISH_STRENGTH)){ a.IncreaseExhaustion(3); Help.TutorialTip(TutorialTopic.HeavyPlateArmor); } } else{ if(blocked_by_root_shell_miss){ B.Add(a.YourVisible() + " root shell blocks " + YourVisible() + " attack. ",this,a); } else{ if(obscured_vision_miss){ B.Add(Your() + " attack goes wide. ",this); } else{ if(!attack_is_part_of_another_action && drive_back_applied && !MovementPrevented(M.tile[target_original_pos])){ B.Add(You("drive") + " " + a.TheName(true) + " back. ",this,a); /*if(!a.HasAttr(AttrType.FROZEN) && !HasAttr(AttrType.FROZEN)){ a.AI_Step(this,true); AI_Step(a); }*/ Tile dest = null; int dir = DirectionOf(target_original_pos); foreach(int next_dir in new List<int>{dir,dir.RotateDir(true),dir.RotateDir(false)}){ Tile t = a.TileInDirection(next_dir); if(t.passable && t.actor() == null && !a.MovementPrevented(t)){ dest = t; break; } } if(dest != null){ a.AI_MoveOrOpen(dest.row,dest.col); if(M.actor[target_original_pos] == null){ AI_MoveOrOpen(target_original_pos.row,target_original_pos.col); } } } else{ if(info.miss != ""){ string s = info.miss + ". "; int pos = -1; do{ pos = s.IndexOf('&'); if(pos != -1){ s = s.Substring(0,pos) + TheName(true) + s.Substring(pos+1); } } while(pos != -1); // do{ pos = s.IndexOf('*'); if(pos != -1){ s = s.Substring(0,pos) + a.TheName(true) + s.Substring(pos+1); } } while(pos != -1); B.Add(s,this,a); } else{ B.Add(YouVisible("miss",true) + " " + a.TheName(true) + ". ",this,a); } } } } } if(type == ActorType.SWORDSMAN || type == ActorType.PHANTOM_SWORDMASTER || type == ActorType.ALASI_SOLDIER){ attrs[AttrType.COMBO_ATTACK] = 0; } } else{ string s = info.hit + ". "; if(!attack_is_part_of_another_action && HasFeat(FeatType.NECK_SNAP) && a.HasAttr(AttrType.MEDIUM_HUMANOID) && (IsHiddenFrom(a) || a.IsHelpless())){ if(!a.HasAttr(AttrType.RESIST_NECK_SNAP)){ B.Add(You("silently snap") + " " + a.Your() + " neck. "); a.Kill(); Q1(); return true; } else{ B.Add(You("silently snap") + " " + a.Your() + " neck. "); B.Add("It doesn't seem to affect " + a.the_name + ". "); } } bool crit = false; int crit_chance = 8; //base crit rate is 1/8 if(EquippedWeapon.type == WeaponType.DAGGER && !tile().IsLit()){ crit_chance /= 2; } if(a.EquippedArmor != null && (a.EquippedArmor.status[EquipmentStatus.WEAK_POINT] || a.EquippedArmor.status[EquipmentStatus.DAMAGED] || a.HasAttr(AttrType.SWITCHING_ARMOR))){ crit_chance /= 2; } if(a.HasAttr(AttrType.SUSCEPTIBLE_TO_CRITS)){ //caused by armor mastery crit_chance /= 2; } if(EquippedWeapon.enchantment == EnchantmentType.PRECISION && !EquippedWeapon.status[EquipmentStatus.NEGATED]){ crit_chance /= 2; } if(drive_back_nowhere_to_run){ crit_chance /= 2; } if(crit_chance <= 1 || R.OneIn(crit_chance)){ crit = true; } int pos = -1; do{ pos = s.IndexOf('&'); if(pos != -1){ s = s.Substring(0,pos) + TheName(true) + s.Substring(pos+1); } } while(pos != -1); // do{ pos = s.IndexOf('*'); if(pos != -1){ s = s.Substring(0,pos) + a.TheName(true) + s.Substring(pos+1); } } while(pos != -1); int dice = info.damage.dice; if(sneak_attack && crit && this == player){ if(!a.HasAttr(AttrType.NONLIVING,AttrType.PLANTLIKE,AttrType.BOSS_MONSTER) && a.type != ActorType.CYCLOPEAN_TITAN){ switch(EquippedWeapon.type){ //todo: should this check for shielded/blocking? case WeaponType.SWORD: B.Add("You run " + a.TheName(true) + " through! "); break; case WeaponType.MACE: B.Add("You bash " + a.YourVisible() + " head in! "); break; case WeaponType.DAGGER: B.Add("You pierce one of " + a.YourVisible() + " vital organs! "); break; case WeaponType.STAFF: B.Add("You bring your staff down on " + a.YourVisible() + " head with a loud crack! "); break; case WeaponType.BOW: B.Add("You choke " + a.TheName(true) + " with your bowstring! "); break; default: break; } Help.TutorialTip(TutorialTopic.InstantKills); MakeNoise(6); if(a.type == ActorType.BERSERKER && a.target == this){ a.attrs[AttrType.SHIELDED] = 0; a.TakeDamage(DamageType.NORMAL,DamageClass.NO_TYPE,a.curhp,this); } else{ a.Kill(); } if(!attack_is_part_of_another_action){ Q1(); } return true; } } if(sneak_attack && (this == player || a == player)){ B.Add(YouVisible("strike") + " from hiding! "); if(type != ActorType.PLAYER){ if(a == player && attrs[AttrType.TURNS_VISIBLE] >= 0){ B.PrintAll(); } attrs[AttrType.TURNS_VISIBLE] = -1; attrs[AttrType.NOTICED] = 1; attrs[AttrType.DANGER_SENSED] = 1; } else{ a.player_visibility_duration = -1; a.attrs[AttrType.PLAYER_NOTICED] = 1; } } if(a == player){ if(a.HasAttr(AttrType.SWITCHING_ARMOR)){ B.Add("You're unguarded! "); } else{ if(a.EquippedArmor.status[EquipmentStatus.DAMAGED]){ B.Add("Your damaged armor leaves you open! "); } else{ if(crit && R.CoinFlip()){ if(a.EquippedArmor.status[EquipmentStatus.WEAK_POINT]){ B.Add(TheName(true) + " finds a weak point. "); } } } } } if(mace_through_armor){ if(type == ActorType.CRUSADING_KNIGHT || type == ActorType.PHANTOM_CRUSADER){ B.Add(YourVisible() + " huge mace punches through " + a.YourVisible() + " armor. ",this,a); } else{ B.Add(YourVisible() + " mace punches through " + a.YourVisible() + " armor. ",this,a); } } else{ B.Add(s,this,a); } if(crit && info.crit != AttackEffect.NO_CRIT){ if(this == player || a == player){ Help.TutorialTip(TutorialTopic.CriticalHits); } } if(a == player && !player.CanSee(this)){ Screen.AnimateMapCell(row,col,new colorchar('?',Color.DarkGray),50); } if(a.type == ActorType.GHOST && EquippedWeapon.enchantment != EnchantmentType.NO_ENCHANTMENT && !EquippedWeapon.status[EquipmentStatus.NEGATED]){ EquippedWeapon.status[EquipmentStatus.NEGATED] = true; B.Add(Your() + " " + EquippedWeapon.NameWithEnchantment() + "'s magic is suppressed! ",this); Help.TutorialTip(TutorialTopic.Negated); } if(!Help.displayed[TutorialTopic.SwitchingEquipment] && this == player && a.Is(ActorType.SPORE_POD,ActorType.SKELETON,ActorType.STONE_GOLEM,ActorType.MECHANICAL_KNIGHT,ActorType.MACHINE_OF_WAR) && EquippedWeapon.type == WeaponType.SWORD){ Help.TutorialTip(TutorialTopic.SwitchingEquipment); } int dmg = R.Roll(dice,6); bool no_max_damage_message = false; List<AttackEffect> effects = new List<AttackEffect>(); if(crit && info.crit != AttackEffect.NO_CRIT){ effects.AddUnique(info.crit); } if(info.effects != null){ foreach(AttackEffect effect in info.effects){ effects.AddUnique(effect); } } if(type == ActorType.DEMON_LORD && DistanceFrom(a) == 2){ effects.AddUnique(AttackEffect.PULL); } if(type == ActorType.SWORDSMAN && attrs[AttrType.COMBO_ATTACK] == 2){ effects.AddUnique(AttackEffect.BLEED); effects.AddUnique(AttackEffect.STRONG_KNOCKBACK); } if(type == ActorType.PHANTOM_SWORDMASTER && attrs[AttrType.COMBO_ATTACK] == 2){ effects.AddUnique(AttackEffect.PERCENT_DAMAGE); effects.AddUnique(AttackEffect.STRONG_KNOCKBACK); } if(type == ActorType.ALASI_SOLDIER){ if(attrs[AttrType.COMBO_ATTACK] == 1){ effects.AddUnique(AttackEffect.ONE_TURN_STUN); } else{ if(attrs[AttrType.COMBO_ATTACK] == 2){ effects.AddUnique(AttackEffect.ONE_TURN_PARALYZE); } } } if(type == ActorType.WILD_BOAR && HasAttr(AttrType.COOLDOWN_1)){ effects.AddUnique(AttackEffect.FLING); } if(type == ActorType.ALASI_SENTINEL && R.OneIn(3)){ effects.AddUnique(AttackEffect.FLING); } if(this == player && a.type == ActorType.CYCLOPEAN_TITAN && crit){ effects = new List<AttackEffect>(); //remove all other effects (so far) and check for edged weapons if(EquippedWeapon == Sword || EquippedWeapon == Dagger){ effects.Add(AttackEffect.PERMANENT_BLIND); } } if(EquippedWeapon.status[EquipmentStatus.POISONED]){ effects.AddUnique(AttackEffect.POISON); } if(HasAttr(AttrType.PSEUDO_VAMPIRIC)){ effects.AddUnique(AttackEffect.DRAIN_LIFE); } if(HasAttr(AttrType.BRUTISH_STRENGTH)){ effects.AddUnique(AttackEffect.MAX_DAMAGE); effects.AddUnique(AttackEffect.STRONG_KNOCKBACK); effects.Remove(AttackEffect.KNOCKBACK); //strong knockback replaces these effects.Remove(AttackEffect.TRIP); effects.Remove(AttackEffect.FLING); } if(EquippedWeapon != null && !EquippedWeapon.status[EquipmentStatus.NEGATED]){ switch(EquippedWeapon.enchantment){ case EnchantmentType.CHILLING: effects.AddUnique(AttackEffect.CHILL); break; case EnchantmentType.DISRUPTION: effects.AddUnique(AttackEffect.DISRUPTION); //not entirely sure that these should be crit effects break; case EnchantmentType.VICTORY: if(a.maxhp > 1){ // no illusions, phantoms, or minions effects.AddUnique(AttackEffect.VICTORY); } break; } } if(type == ActorType.SKITTERMOSS && HasAttr(AttrType.COOLDOWN_1)){ effects.Remove(AttackEffect.INFEST); } if(a.HasAttr(AttrType.NONLIVING)){ effects.Remove(AttackEffect.DRAIN_LIFE); } foreach(AttackEffect effect in effects){ //pre-damage effects - these can alter the amount of damage. switch(effect){ case AttackEffect.MAX_DAMAGE: dmg = Math.Max(dmg,dice * 6); break; case AttackEffect.PERCENT_DAMAGE: dmg = Math.Max(dmg,(a.maxhp+1)/2); no_max_damage_message = true; if(!EquippedWeapon.status[EquipmentStatus.DULLED]){ if(this == player){ B.Add("Your sword cuts deep! "); } else{ B.Add(Your() + " attack cuts deep! ",this); } } break; case AttackEffect.ONE_HP: dmg = a.curhp - 1; if(a.HasAttr(AttrType.VULNERABLE)){ a.attrs[AttrType.VULNERABLE] = 0; } if(a == player){ B.Add("You shudder. "); } no_max_damage_message = true; break; } } if(dice < 2){ no_max_damage_message = true; } if(a.type == ActorType.SPORE_POD && EquippedWeapon.IsBlunt()){ no_max_damage_message = true; } if(EquippedWeapon.status[EquipmentStatus.MERCIFUL]){ no_max_damage_message = true; } if(a.HasAttr(AttrType.RESIST_WEAPONS) && EquippedWeapon.type != WeaponType.NO_WEAPON){ B.Add("Your " + EquippedWeapon.NameWithoutEnchantment() + " isn't very effective. "); dmg = dice; //minimum damage } else{ if(EquippedWeapon.status[EquipmentStatus.DULLED]){ B.Add("Your dull " + EquippedWeapon.NameWithoutEnchantment() + " isn't very effective. "); dmg = dice; //minimum damage } else{ if(type == ActorType.MUD_TENTACLE){ //getting surrounded by these guys should be dangerous, but not simply because of their damage. dmg = dice; } } } if(dmg >= dice * 6 && !no_max_damage_message){ if(this == player){ B.Add("It was a good hit! "); } else{ if(a == player){ B.Add("Ow! "); } } } dmg += TotalSkill(SkillType.COMBAT); if(a.type == ActorType.SPORE_POD && EquippedWeapon.IsBlunt()){ dmg = 0; dice = 0; effects.AddUnique(AttackEffect.STRONG_KNOCKBACK); B.Add("Your " + EquippedWeapon.NameWithoutEnchantment() + " knocks the spore pod away. ",a); } if(EquippedWeapon.status[EquipmentStatus.MERCIFUL] && dmg >= a.curhp){ dmg = a.curhp - 1; B.Add("Your " + EquippedWeapon.NameWithoutEnchantment() + " refuses to finish " + a.TheName(true) + ". "); B.Print(true); } if(a.HasAttr(AttrType.DULLS_BLADES) && R.CoinFlip() && (EquippedWeapon == Sword || EquippedWeapon == Dagger)){ EquippedWeapon.status[EquipmentStatus.DULLED] = true; B.Add(Your() + " " + EquippedWeapon.NameWithEnchantment() + " becomes dull! ",this); Help.TutorialTip(TutorialTopic.Dulled); } if(a.type == ActorType.CORROSIVE_OOZE && R.CoinFlip() && (EquippedWeapon == Sword || EquippedWeapon == Dagger || EquippedWeapon == Mace)){ EquippedWeapon.status[EquipmentStatus.DULLED] = true; B.Add("The acid dulls " + Your() + " " + EquippedWeapon.NameWithEnchantment() + "! ",this); Help.TutorialTip(TutorialTopic.Acidified); Help.TutorialTip(TutorialTopic.Dulled); } if(a.HasAttr(AttrType.CAN_POISON_WEAPONS) && R.CoinFlip() && EquippedWeapon.type != WeaponType.NO_WEAPON && !EquippedWeapon.status[EquipmentStatus.POISONED]){ EquippedWeapon.status[EquipmentStatus.POISONED] = true; weapon_just_poisoned = true; B.Add(Your() + " " + EquippedWeapon.NameWithEnchantment() + " is covered in poison! ",this); } int r = a.row; int c = a.col; bool still_alive = true; bool knockback_effect = effects.Contains(AttackEffect.KNOCKBACK) || effects.Contains(AttackEffect.STRONG_KNOCKBACK) || effects.Contains(AttackEffect.TRIP) || effects.Contains(AttackEffect.FLING) || effects.Contains(AttackEffect.SWAP_POSITIONS) || effects.Contains(AttackEffect.DRAIN_LIFE); if(knockback_effect){ a.attrs[AttrType.TURN_INTO_CORPSE]++; } Color blood = a.BloodColor(); bool homunculus = a.type == ActorType.HOMUNCULUS; if(dmg > 0){ Damage damage = new Damage(info.damage.type,info.damage.damclass,true,this,dmg); damage.weapon_used = EquippedWeapon.type; still_alive = a.TakeDamage(damage,a_name); } if(homunculus){ //todo: or will this happen on any major damage, not just melee attacks? M.tile[target_original_pos].AddFeature(FeatureType.OIL); } else{ if(blood != Color.Black && (!still_alive || !a.HasAttr(AttrType.FROZEN,AttrType.INVULNERABLE))){ /*List<Tile> valid = new List<Tile>{M.tile[target_original_pos]}; for(int i=-1;i<=1;++i){ valid.Add(M.tile[target_original_pos].TileInDirection(original_pos.DirectionOf(target_original_pos).RotateDir(true,i))); } for(int i=dmg/10;i>0;--i){ Tile t = valid.RemoveRandom(); if(t.Is(TileType.WALL) || t.name == "floor"){ t.color = blood; } }*/ List<Tile> cone = M.tile[target_original_pos].GetCone(original_pos.DirectionOf(target_original_pos),dmg>=20? 2 : 1,false); cone.Add(M.tile[target_original_pos].TileInDirection(original_pos.DirectionOf(target_original_pos))); cone.Add(M.tile[target_original_pos].TileInDirection(original_pos.DirectionOf(target_original_pos))); cone.Add(M.tile[target_original_pos].TileInDirection(original_pos.DirectionOf(target_original_pos))); for(int i=(dmg-5)/5;i>0;--i){ if(cone.Count == 0){ break; } Tile t = cone.Random(); while(cone.Remove(t)){ } //remove all if(t.Is(TileType.WALL) || t.name == "floor"){ t.color = blood; switch(blood){ case Color.DarkRed: M.aesthetics[t.p] = AestheticFeature.BloodDarkRed; break; default: M.aesthetics[t.p] = AestheticFeature.BloodOther; break; } } } } } if(still_alive){ //post-damage crit effects that require the target to still be alive foreach(AttackEffect effect in effects){ if(still_alive){ switch(effect){ //todo: some of these messages shouldn't be printed if the effect already exists case AttackEffect.CONFUSE: a.ApplyStatus(AttrType.CONFUSED,R.Between(2,3)*100); break; case AttackEffect.BLEED: if(!a.HasAttr(AttrType.NONLIVING,AttrType.FROZEN)){ if(a.HasAttr(AttrType.BLEEDING)){ if(a == player){ if(a.attrs[AttrType.BLEEDING] > 15){ B.Add("Your bleeding worsens. "); } else{ B.Add("You're bleeding badly now! "); } } else{ B.Add(a.YouAre() + " bleeding badly! ",a); } } a.attrs[AttrType.BLEEDING] += R.Between(10,15); if(a.attrs[AttrType.BLEEDING] > 25){ a.attrs[AttrType.BLEEDING] = 25; //this seems like a reasonable cap, so repeated bleed effects don't just last *forever*. } if(a == player){ Help.TutorialTip(TutorialTopic.Bleeding); } } break; case AttackEffect.BLIND: a.ApplyStatus(AttrType.BLIND,R.Between(5,7)*100); //B.Add(a.YouAre() + " blinded! ",a); //a.RefreshDuration(AttrType.BLIND,R.Between(5,7)*100); break; case AttackEffect.PERMANENT_BLIND: { if(!a.HasAttr(AttrType.COOLDOWN_1)){ B.Add("You drive your " + EquippedWeapon.NameWithoutEnchantment() + " into its eye, blinding it! "); Q.KillEvents(a,AttrType.BLIND); a.attrs[AttrType.BLIND] = 1; a.attrs[AttrType.COOLDOWN_1] = 1; } break; } case AttackEffect.DIM_VISION: if(a.ResistedBySpirit()){ B.Add(a.Your() + " vision is dimmed, but only for a moment. ",a); } else{ B.Add(a.Your() + " vision is dimmed. ",a); a.RefreshDuration(AttrType.DIM_VISION,(R.Roll(2,20)+20)*100); } break; case AttackEffect.CHILL: if(!a.HasAttr(AttrType.IMMUNE_COLD)){ B.Add(a.the_name + " is chilled. ",a); if(!a.HasAttr(AttrType.CHILLED)){ a.attrs[AttrType.CHILLED] = 1; } else{ a.attrs[AttrType.CHILLED] *= 2; } if(!a.TakeDamage(DamageType.COLD,DamageClass.MAGICAL,a.attrs[AttrType.CHILLED],this)){ still_alive = false; } } break; case AttackEffect.DISRUPTION: if(a.HasAttr(AttrType.NONLIVING)){ B.Add(a.the_name + " is disrupted. ",a); if(!a.TakeDamage(DamageType.MAGIC,DamageClass.MAGICAL,a.maxhp / 5,this)){ still_alive = false; } } break; case AttackEffect.FREEZE: a.tile().ApplyEffect(DamageType.COLD); a.ApplyFreezing(); break; case AttackEffect.GRAB: if(!HasAttr(AttrType.GRABBING) && DistanceFrom(a) == 1 && !a.HasAttr(AttrType.FROZEN)){ a.attrs[AttrType.GRABBED]++; attrs[AttrType.GRABBING] = DirectionOf(a); B.Add(YouVisible("grab") + " " + a.TheName(true) + ". ",this,a); if(a == player){ Help.TutorialTip(TutorialTopic.Grabbed); } } break; case AttackEffect.POISON: if(!a.HasAttr(AttrType.NONLIVING,AttrType.CAN_POISON_WEAPONS,AttrType.INVULNERABLE,AttrType.FROZEN)){ a.ApplyStatus(AttrType.POISONED,(R.Roll(2,6)+2)*100); } break; case AttackEffect.PARALYZE: if(!a.HasAttr(AttrType.NONLIVING) || type != ActorType.CARRION_CRAWLER){ if(a.ResistedBySpirit()){ B.Add(a.Your() + " muscles stiffen, but only for a moment. ",a); } else{ if(a == player){ B.Add("You suddenly can't move! "); } else{ B.Add(a.YouAre() + " paralyzed. ",a); } a.attrs[AttrType.PARALYZED] = R.Between(3,5); } } break; case AttackEffect.ONE_TURN_PARALYZE: Event e = Q.FindAttrEvent(a,AttrType.STUNNED); if(e != null && e.delay == 100 && e.TimeToExecute() == Q.turn){ //if the target was hit with a 1-turn stun that's about to expire, don't print a message for it. e.msg = ""; } if(a.ResistedBySpirit()){ B.Add(a.Your() + " muscles stiffen, but only for a moment. ",a); } else{ B.Add(a.YouAre() + " paralyzed! ",a); a.attrs[AttrType.PARALYZED] = 2; //setting it to 1 means it would end immediately } break; case AttackEffect.INFLICT_VULNERABILITY: a.ApplyStatus(AttrType.VULNERABLE,R.Between(2,4)*100); /*B.Add(a.You("become") + " vulnerable. ",a); a.RefreshDuration(AttrType.VULNERABLE,R.Between(2,4)*100);*/ if(a == player){ Help.TutorialTip(TutorialTopic.Vulnerable); } break; case AttackEffect.IGNITE: break; case AttackEffect.INFEST: if(a == player && !a.EquippedArmor.status[EquipmentStatus.INFESTED]){ B.Add("Thousands of insects crawl into your " + a.EquippedArmor.NameWithoutEnchantment() + "! "); a.EquippedArmor.status[EquipmentStatus.INFESTED] = true; Help.TutorialTip(TutorialTopic.Infested); } break; case AttackEffect.SLOW: a.ApplyStatus(AttrType.SLOWED,R.Between(4,6)*100); break; case AttackEffect.REDUCE_ACCURACY: //also about 2d4 turns? break; case AttackEffect.SLIME: B.Add(a.YouAre() + " covered in slime. ",a); a.attrs[AttrType.SLIMED] = 1; if(a == player){ Help.TutorialTip(TutorialTopic.Slimed); } break; case AttackEffect.STUN: //2d3 turns, at most { a.ApplyStatus(AttrType.STUNNED,R.Roll(2,3)*100); /*B.Add(a.YouAre() + " stunned! ",a); a.RefreshDuration(AttrType.STUNNED,a.DurationOfMagicalEffect(R.Roll(2,3)) * 100,a.YouAre() + " no longer stunned. ",a);*/ if(a == player){ Help.TutorialTip(TutorialTopic.Stunned); } break; } case AttackEffect.ONE_TURN_STUN: { a.ApplyStatus(AttrType.STUNNED,100); /*B.Add(a.YouAre() + " stunned! ",a); a.RefreshDuration(AttrType.STUNNED,100,a.YouAre() + " no longer stunned. ",a);*/ if(a == player){ Help.TutorialTip(TutorialTopic.Stunned); } break; } case AttackEffect.SILENCE: { if(a.ResistedBySpirit()){ if(!HasAttr(AttrType.SILENCED)){ B.Add(a.You("resist") + " being silenced. ",a); } } else{ if(!HasAttr(AttrType.SILENCED)){ B.Add(TheName(true) + " silences " + a.the_name + ". ",a); } a.RefreshDuration(AttrType.SILENCED,R.Between(3,4)*100,a.YouAre() + " no longer silenced. ",a); } if(a == player){ Help.TutorialTip(TutorialTopic.Silenced); } break; } case AttackEffect.WEAK_POINT: if(!a.EquippedArmor.status[EquipmentStatus.WEAK_POINT] && a == player){ a.EquippedArmor.status[EquipmentStatus.WEAK_POINT] = true; B.Add(YouVisible("expose") + " a weak point on your armor! ",this); Help.TutorialTip(TutorialTopic.WeakPoint); } break; case AttackEffect.WORN_OUT: if(a == player && !a.EquippedArmor.status[EquipmentStatus.DAMAGED]){ if(a.EquippedArmor.status[EquipmentStatus.WORN_OUT]){ a.EquippedArmor.status[EquipmentStatus.WORN_OUT] = false; a.EquippedArmor.status[EquipmentStatus.WEAK_POINT] = false; a.EquippedArmor.status[EquipmentStatus.DAMAGED] = true; B.Add(a.Your() + " " + a.EquippedArmor.NameWithEnchantment() + " is damaged! "); Help.TutorialTip(TutorialTopic.Damaged); } else{ a.EquippedArmor.status[EquipmentStatus.WORN_OUT] = true; B.Add(a.Your() + " " + a.EquippedArmor.NameWithEnchantment() + " looks worn out. "); Help.TutorialTip(TutorialTopic.WornOut); } } break; case AttackEffect.ACID: if(a == player && !a.HasAttr(AttrType.ACIDIFIED) && R.CoinFlip()){ a.RefreshDuration(AttrType.ACIDIFIED,300); if(a.EquippedArmor != a.Leather && !a.EquippedArmor.status[EquipmentStatus.DAMAGED]){ B.Add("The acid hisses as it touches your " + a.EquippedArmor.NameWithEnchantment() + "! "); if(a.EquippedArmor.status[EquipmentStatus.WORN_OUT]){ a.EquippedArmor.status[EquipmentStatus.WORN_OUT] = false; a.EquippedArmor.status[EquipmentStatus.WEAK_POINT] = false; a.EquippedArmor.status[EquipmentStatus.DAMAGED] = true; B.Add(a.Your() + " " + a.EquippedArmor.NameWithEnchantment() + " is damaged! "); } else{ a.EquippedArmor.status[EquipmentStatus.WORN_OUT] = true; B.Add(a.Your() + " " + a.EquippedArmor.NameWithEnchantment() + " looks worn out. "); } Help.TutorialTip(TutorialTopic.Acidified); } } break; case AttackEffect.PULL: { List<Tile> tiles = tile().NeighborsBetween(a.row,a.col).Where(x=>x.actor() == null && x.passable); if(tiles.Count > 0){ Tile t = tiles.Random(); if(!a.MovementPrevented(t)){ B.Add(TheName(true) + " pulls " + a.TheName(true) + " closer. ",this,a); a.Move(t.row,t.col); } } break; } case AttackEffect.STEAL: { if(a.inv != null && a.inv.Count > 0){ Item i = a.inv.Random(); Item stolen = i; if(i.quantity > 1){ stolen = new Item(i,i.row,i.col); stolen.revealed_by_light = i.revealed_by_light; i.quantity--; } else{ a.inv.Remove(stolen); } GetItem(stolen); B.Add(YouVisible("steal") + " " + a.YourVisible() + " " + stolen.SingularName() + "! ",this,a); B.PrintAll(); } break; } case AttackEffect.EXHAUST: { if(a == player){ B.Add("You feel fatigued. "); } a.IncreaseExhaustion(R.Roll(2,4)); break; } } } } } foreach(AttackEffect effect in effects){ //effects that don't care whether the target is still alive switch(effect){ case AttackEffect.DRAIN_LIFE: { if(curhp < maxhp){ curhp += 10; if(curhp > maxhp){ curhp = maxhp; } B.Add(You("drain") + " some life from " + a.TheName(true) + ". ",this); } break; } case AttackEffect.VICTORY: if(!still_alive){ curhp += 5; if(curhp > maxhp){ curhp = maxhp; } } break; case AttackEffect.STALAGMITES: { List<Tile> tiles = new List<Tile>(); foreach(Tile t in M.tile[r,c].TilesWithinDistance(1)){ //if(t.actor() == null && (t.type == TileType.FLOOR || t.type == TileType.STALAGMITE)){ if(t.actor() == null && t.inv == null && (t.IsTrap() || t.Is(TileType.FLOOR,TileType.GRAVE_DIRT,TileType.GRAVEL,TileType.STALAGMITE))){ if(R.CoinFlip()){ tiles.Add(t); } } } foreach(Tile t in tiles){ if(t.type == TileType.STALAGMITE){ Q.KillEvents(t,EventType.STALAGMITE); } else{ TileType previous_type = t.type; t.Toggle(this,TileType.STALAGMITE); t.toggles_into = previous_type; } } Q.Add(new Event(tiles,150,EventType.STALAGMITE)); break; } case AttackEffect.MAKE_NOISE: break; case AttackEffect.SWAP_POSITIONS: if(original_pos.DistanceFrom(target_original_pos) == 1 && p.Equals(original_pos) && M.actor[target_original_pos] != null && !M.actor[target_original_pos].HasAttr(AttrType.IMMOBILE)){ B.Add(YouVisible("move") + " past " + M.actor[target_original_pos].TheName(true) + ". ",this,M.actor[target_original_pos]); Move(target_original_pos.row,target_original_pos.col); } break; case AttackEffect.TRIP: if(!a.HasAttr(AttrType.FLYING) && (a.curhp > 0 || !a.HasAttr(AttrType.NO_CORPSE_KNOCKBACK))){ B.Add(YouVisible("trip") + " " + a.TheName(true) + ". ",this,a); a.IncreaseExhaustion(R.Between(2,4)); a.CollideWith(a.tile());//todo: if it's a corpse, ONLY trip it if something is going to happen when it collides with the floor. } break; case AttackEffect.KNOCKBACK: if(a.curhp > 0 || !a.HasAttr(AttrType.NO_CORPSE_KNOCKBACK)){ KnockObjectBack(a,3,this); } break; case AttackEffect.STRONG_KNOCKBACK: if(a.curhp > 0 || !a.HasAttr(AttrType.NO_CORPSE_KNOCKBACK)){ KnockObjectBack(a,5,this); } break; case AttackEffect.FLING: if(a.curhp > 0 || !a.HasAttr(AttrType.NO_CORPSE_KNOCKBACK)){ attrs[AttrType.JUST_FLUNG] = 1; int dir = DirectionOf(a).RotateDir(true,4); Tile t = null; if(tile().p.PosInDir(dir).PosInDir(dir).BoundsCheck(M.tile,true)){ Tile t2 = tile().TileInDirection(dir).TileInDirection(dir); if(HasLOE(t2)){ t = t2; } } if(t == null){ if(tile().p.PosInDir(dir).BoundsCheck(M.tile,false)){ Tile t2 = tile().TileInDirection(dir); if(HasLOE(t2)){ t = t2; } } } if(t == null){ t = tile(); } B.Add(YouVisible("fling") + " " + a.TheName(true) + "! ",this,a); foreach(Tile nearby in M.ReachableTilesByDistance(t.row,t.col,false)){ if(nearby.passable && nearby.actor() == null && HasLOE(nearby)){ a.Move(nearby.row,nearby.col); a.CollideWith(nearby); break; } } } break; } } if(knockback_effect){ if(a.curhp > 0 && this != player){ target_location = target.tile(); } a.CorpseCleanup(); } if(type == ActorType.SWORDSMAN || type == ActorType.PHANTOM_SWORDMASTER || type == ActorType.ALASI_SOLDIER){ if(attrs[AttrType.COMBO_ATTACK] == 1 && (type == ActorType.SWORDSMAN || type == ActorType.PHANTOM_SWORDMASTER)){ B.Add(the_name + " prepares a devastating strike! ",this); } attrs[AttrType.COMBO_ATTACK]++; if(attrs[AttrType.COMBO_ATTACK] == 3){ //all these have 3-part combos attrs[AttrType.COMBO_ATTACK] = 0; } } } /*if(!hit && HasAttr(AttrType.BRUTISH_STRENGTH) && p.Equals(original_pos) && M.actor[target_original_pos] != null){ Actor a2 = M.actor[target_original_pos]; if(a2.HasAttr(AttrType.NO_CORPSE_KNOCKBACK) && a2.maxhp == 1){ B.Add(YouVisible("push",true) + " " + a2.TheName(true) + ". ",this,a2); a2.TakeDamage(DamageType.NORMAL,DamageClass.PHYSICAL,false,1,this); } else{ a2.attrs[AttrType.TURN_INTO_CORPSE]++; KnockObjectBack(a2,5); a2.CorpseCleanup(); } }*/ if(!hit && sneak_attack && this != player){ attrs[AttrType.TURNS_VISIBLE] = -1; attrs[AttrType.NOTICED]++; } if(!attack_is_part_of_another_action && hit && HasAttr(AttrType.BRUTISH_STRENGTH) && p.Equals(original_pos) && M.actor[target_original_pos] == null && DistanceFrom(target_original_pos) == 1 && !MovementPrevented(M.tile[target_original_pos])){ Tile t = M.tile[target_original_pos]; if(t.IsTrap()){ t.SetName(Tile.Prototype(t.type).name); t.TurnToFloor(); } if(HasFeat(FeatType.WHIRLWIND_STYLE)){ WhirlwindMove(t.row,t.col); } else{ Move(t.row,t.col); } } if(hit && EquippedWeapon.enchantment == EnchantmentType.ECHOES && !EquippedWeapon.status[EquipmentStatus.NEGATED]){ List<Tile> line = GetBestExtendedLineOfEffect(target_original_pos.row,target_original_pos.col); int idx = line.IndexOf(M.tile[target_original_pos]); if(idx != -1 && line.Count > idx + 1){ Actor next = line[idx+1].actor(); if(next != null && next != this){ Attack(attack_idx,next,true); } } } //if(!attack_is_part_of_another_action && EquippedWeapon == Staff && p.Equals(original_pos) && a_moved_last_turn && !HasAttr(AttrType.IMMOBILE) && M.tile[target_original_pos].passable && (M.actor[target_original_pos] == null || !M.actor[target_original_pos].HasAttr(AttrType.IMMOBILE))){ if(!attack_is_part_of_another_action && EquippedWeapon == Staff && p.Equals(original_pos) && a_moved_last_turn && !MovementPrevented(M.tile[target_original_pos]) && M.tile[target_original_pos].passable && (M.actor[target_original_pos] == null || !M.actor[target_original_pos].MovementPrevented(this))){ if(M.actor[target_original_pos] != null){ M.actor[target_original_pos].attrs[AttrType.TURNS_HERE]++; //this is a hack to prevent fast monsters from swapping *back* on the next hit. } if(HasFeat(FeatType.WHIRLWIND_STYLE)){ WhirlwindMove(target_original_pos.row,target_original_pos.col,true,new List<Actor>{M.actor[target_original_pos]}); //whirlwind move, but don't attack the original target again } else{ Move(target_original_pos.row,target_original_pos.col); } } if(!attack_is_part_of_another_action && EquippedWeapon.status[EquipmentStatus.POISONED] && !weapon_just_poisoned && R.OneIn(16)){ ApplyStatus(AttrType.POISONED,(R.Roll(2,6)+2)*100,"You manage to poison yourself with your " + EquippedWeapon.NameWithoutEnchantment() + ". ","","You resist the poison dripping from your " + EquippedWeapon.NameWithoutEnchantment() + ". "); } if(!attack_is_part_of_another_action && EquippedWeapon.status[EquipmentStatus.HEAVY] && R.CoinFlip() && !HasAttr(AttrType.BRUTISH_STRENGTH)){ B.Add("Attacking with your heavy " + EquippedWeapon.NameWithoutEnchantment() + " exhausts you. "); IncreaseExhaustion(5); } MakeNoise(6); if(!attack_is_part_of_another_action){ Q.Add(new Event(this,info.cost)); } return hit; }
public Actor(Actor a,int r,int c) { type = a.type; name = a.name; the_name = a.the_name; a_name = a.a_name; symbol = a.symbol; color = a.color; maxhp = a.maxhp; curhp = maxhp; maxmp = a.maxmp; curmp = maxmp; speed = a.speed; light_radius = a.light_radius; target = null; inv = new List<Item>(); row = r; col = c; target_location = null; time_of_last_action = 0; recover_time = 0; player_visibility_duration = 0; weapons.AddFirst(new Weapon(WeaponType.NO_WEAPON)); armors.AddFirst(new Armor(ArmorType.NO_ARMOR)); attrs = new Dict<AttrType, int>(a.attrs); skills = new Dict<SkillType,int>(a.skills); feats = new Dict<FeatType,bool>(a.feats); spells = new Dict<SpellType,bool>(a.spells); exhaustion = 0; sprite_offset = a.sprite_offset; }
public bool AI_Step(PhysicalObject obj,bool flee) { if(HasAttr(AttrType.IMMOBILE) || (type == ActorType.MECHANICAL_KNIGHT && attrs[AttrType.COOLDOWN_1] == 2)){ return false; } if(SlippedOrStruggled()){ return true; } List<int> dirs = new List<int>(); List<int> sideways_directions = new List<int>(); AI_Step_Build_Direction_Lists(tile(),obj,flee,dirs,sideways_directions); List<int> partially_blocked_dirs = new List<int>(); foreach(int i in dirs){ if(ActorInDirection(i) != null && ActorInDirection(i).IsHiddenFrom(this)){ player_visibility_duration = -1; if(ActorInDirection(i) == player){ attrs[AttrType.PLAYER_NOTICED]++; } target = player; //not extensible yet target_location = M.tile[player.row,player.col]; string walks = " walks straight into you! "; if(HasAttr(AttrType.FLYING)){ walks = " flies straight into you! "; } if(!IsHiddenFrom(player)){ B.Add(TheName(true) + walks); if(!HasAttr(AttrType.MINDLESS) && player.CanSee(this)){ B.Add(the_name + " looks startled. "); } } else{ attrs[AttrType.TURNS_VISIBLE] = -1; attrs[AttrType.NOTICED]++; B.Add(AName(true) + walks); if(!HasAttr(AttrType.MINDLESS) && player.CanSee(this)){ B.Add(the_name + " looks just as surprised as you. "); } } return true; } Tile t = TileInDirection(i); if(t.Is(TileType.RUBBLE) && (path == null || path.Count == 0 || t != M.tile[path[0]])){ //other tiles might go here eventually partially_blocked_dirs.Add(i); } else{ if(AI_WillingToMove(tile(),t,obj) && AI_MoveOrOpen(i)){ return true; } } } foreach(int i in partially_blocked_dirs){ if(AI_WillingToMove(tile(),TileInDirection(i),obj) && AI_MoveOrOpen(i)){ return true; } } foreach(int i in sideways_directions){ if(AI_WillingToMove(tile(),TileInDirection(i),obj) && AI_MoveOrOpen(i)){ return true; } } return false; }
private bool CheckForFear(Tile t) { int least_frightening_value = 99; List<Tile> least_frightening = new List<Tile>(); List<Actor> attackable_actors = new List<Actor>(); foreach(Tile neighbor in TilesAtDistance(1)){ if(neighbor.passable){ if(neighbor.actor() != null && CanSee(neighbor.actor())){ if(!neighbor.actor().HasAttr(AttrType.TERRIFYING)){ attackable_actors.Add(neighbor.actor()); } continue; } int value = 0; foreach(int dir2 in U.EightDirections){ Actor n2 = neighbor.ActorInDirection(dir2); if(n2 != null && n2.HasAttr(AttrType.TERRIFYING) && CanSee(n2)){ ++value; } } if(value < least_frightening_value){ least_frightening_value = value; least_frightening.Clear(); least_frightening.Add(neighbor); } else{ if(value == least_frightening_value){ least_frightening.Add(neighbor); } } } } foreach(Actor a in attackable_actors){ least_frightening.Add(a.tile()); } if(!least_frightening.Contains(t)){ B.Add("You're too afraid! "); List<pos> cells = new List<pos>(); List<colorchar> chars = new List<colorchar>(); foreach(Tile neighbor in TilesAtDistance(1)){ if(neighbor.passable && !least_frightening.Contains(neighbor)){ cells.Add(neighbor.p); colorchar cch = M.VisibleColorChar(neighbor.row,neighbor.col); cch.bgcolor = Color.DarkMagenta; if(!CanSee(neighbor)){ cch.color = Color.Black; cch.c = ' '; } chars.Add(cch); } } AnimateVisibleMapCells(cells,chars); return true; } return false; }
private bool AI_WillingToMove(Tile start,Tile next_step,PhysicalObject final_destination) { if(!next_step.p.BoundsCheck(M.tile,false)){ return false; } int destination_danger = GetDangerRating(next_step); //DESTINATION: DANGER! int danger_threshold = (target != null? 1 : 0); if(destination_danger > danger_threshold && destination_danger > GetDangerRating(start)){ return false; } //so, if it's actually too dangerous, the above check'll handle that. Next, let's check for unnecessary ventures into minor hazards: if(destination_danger > 0 && final_destination != null){ if(!AI_CheckNextStep(next_step,final_destination,false)){ return false; } } if(next_step.Is(TileType.CHASM,TileType.FIRE_RIFT) && (!HasAttr(AttrType.FLYING) || HasAttr(AttrType.DESCENDING))){ return false; } if(HasAttr(AttrType.AVOIDS_LIGHT) && !M.wiz_dark && !M.wiz_lite){ if(next_step.light_value > 0 && start.light_value == 0){ if(!(type == ActorType.DARKNESS_DWELLER && HasAttr(AttrType.COOLDOWN_2))){ return false; } } } return true; }
private bool AI_CheckNextStep(Tile start,PhysicalObject obj,bool flee) { List<int> dirs = new List<int>(); List<int> sideways_directions = new List<int>(); AI_Step_Build_Direction_Lists(start,obj,flee,dirs,sideways_directions); List<int> partially_blocked_dirs = new List<int>(); Tile t = null; foreach(int i in dirs){ t = start.TileInDirection(i); if(t.Is(TileType.RUBBLE)){ //other tiles might go here eventually partially_blocked_dirs.Add(i); } else{ if(AI_WillingToMove(start,t,null)){ return true; } } } foreach(int i in partially_blocked_dirs){ if(AI_WillingToMove(start,TileInDirection(i),null)){ return true; } } return false; }
public Actor(ActorType type_, string name_, string symbol_, Color color_, int maxhp_, int speed_, int level_, int light_radius_) { atype = type_; SetName(name_); symbol = symbol_; color = color_; maxhp = maxhp_; curhp = maxhp; speed = speed_; level = level_; light_radius = light_radius_; target = null; inv = null; target_location = null; time_of_last_action = 0; recover_time = 0; player_visibility_duration = 0; weapons.Insert(0, WeaponType.NO_WEAPON); armors.Insert(0, ArmorType.NO_ARMOR); F = new SpellType[13]; for (int i = 0; i < 13; ++i) { F[i] = SpellType.NO_SPELL; } magic_penalty = 0; }
public bool CollideWith(Tile t) { if(t.Is(TileType.FIREPIT) && !t.Is(FeatureType.SLIME)){ B.Add(You("fall") + " into the fire pit. ",this); ApplyBurning(); } if(IsBurning()){ t.ApplyEffect(DamageType.FIRE); } if(t.Is(FeatureType.SLIME) && !HasAttr(AttrType.SLIMED)){ B.Add(YouAre() + " covered in slime. ",this); attrs[AttrType.SLIMED] = 1; attrs[AttrType.OIL_COVERED] = 0; RefreshDuration(AttrType.BURNING,0); if(this == player){ Help.TutorialTip(TutorialTopic.Slimed); } } else{ if(t.Is(FeatureType.OIL) && !HasAttr(AttrType.SLIMED,AttrType.OIL_COVERED)){ B.Add(YouAre() + " covered in oil. ",this); attrs[AttrType.OIL_COVERED] = 1; if(this == player){ Help.TutorialTip(TutorialTopic.Oiled); } } else{ if(t.IsBurning()){ ApplyBurning(); } } } if(!HasAttr(AttrType.SMALL)){ t.ApplyEffect(DamageType.NORMAL); } return !HasAttr(AttrType.CORPSE); }
public async Task InputAI() { bool no_act = false; if (CanSee(player)) { if (target_location == null && HasAttr(AttrType.BLOODSCENT)) { //orc warmages etc. when they first notice player_visibility_duration = -1; target = player; target_location = M.tile[player.row, player.col]; if ((player.IsWithinSightRangeOf(this) || tile().IsLit()) && player.HasLOS(this)) { B.Add(the_name + "'s gaze meets your eyes! ", this); if (DistanceFrom(player) <= 6) { player.MakeNoise(); } } B.Add(the_name + " snarls loudly. ", this); Q1(); no_act = true; } else { target = player; target_location = M.tile[player.row, player.col]; player_visibility_duration = -1; } } else { if ((IsWithinSightRangeOf(player.row, player.col) || M.tile[player.row, player.col].IsLit()) //if they're stealthed and nearby... && HasLOS(player.row, player.col) && (!player.HasAttr(AttrType.SHADOW_CLOAK) || HasAttr(AttrType.PLAYER_NOTICED) || player.tile().IsLit() || HasAttr(AttrType.BLINDSIGHT))) { int multiplier = HasAttr(AttrType.KEEN_SENSES) ? 5 : 10; //animals etc. are approximately twice as hard to sneak past if (player.Stealth() * DistanceFrom(player) * multiplier - player_visibility_duration++ * 5 < Global.Roll(1, 100)) { player_visibility_duration = -1; attrs[Forays.AttrType.PLAYER_NOTICED]++; target = player; target_location = M.tile[player.row, player.col]; if (group != null) { foreach (Actor a in group) { if (a != this && DistanceFrom(a) < 3) { a.player_visibility_duration = -1; a.attrs[Forays.AttrType.PLAYER_NOTICED]++; a.target = player; a.target_location = M.tile[player.row, player.col]; } } } switch (atype) { case ActorType.RAT: case ActorType.DIRE_RAT: B.Add(TheVisible() + " squeaks at you. "); player.MakeNoise(); break; case ActorType.GOBLIN: case ActorType.GOBLIN_ARCHER: case ActorType.GOBLIN_SHAMAN: B.Add(TheVisible() + " growls. "); player.MakeNoise(); break; case ActorType.BLOOD_MOTH: if (!M.wiz_lite && !M.wiz_dark && player.LightRadius() > 0) { B.Add(the_name + " notices your light. ", this); } break; case ActorType.CULTIST: case ActorType.ROBED_ZEALOT: B.Add(TheVisible() + " yells. "); player.MakeNoise(); break; case ActorType.ZOMBIE: B.Add(TheVisible() + " moans. Uhhhhhhghhh. "); player.MakeNoise(); break; case ActorType.WOLF: B.Add(TheVisible() + " snarls at you. "); player.MakeNoise(); break; case ActorType.FROSTLING: B.Add(TheVisible() + " makes a chittering sound. "); player.MakeNoise(); break; case ActorType.SWORDSMAN: case ActorType.BERSERKER: case ActorType.CRUSADING_KNIGHT: B.Add(TheVisible() + " shouts. "); player.MakeNoise(); break; case ActorType.BANSHEE: B.Add(TheVisible() + " shrieks. "); player.MakeNoise(); break; case ActorType.WARG: B.Add(TheVisible() + " howls. "); player.MakeNoise(); break; case ActorType.DERANGED_ASCETIC: B.Add(TheVisible() + " starts babbling incoherently. "); break; case ActorType.CAVERN_HAG: B.Add(TheVisible() + " cackles. "); player.MakeNoise(); break; case ActorType.COMPY: B.Add(TheVisible() + " squeaks. "); player.MakeNoise(); break; case ActorType.OGRE: B.Add(TheVisible() + " bellows at you. "); player.MakeNoise(); break; case ActorType.SHADOW: B.Add(TheVisible() + " hisses faintly. "); break; case ActorType.ORC_GRENADIER: case ActorType.ORC_WARMAGE: B.Add(TheVisible() + " snarls loudly. "); player.MakeNoise(); break; case ActorType.ENTRANCER: B.Add(the_name + " stares at you for a moment. ", this); break; case ActorType.STONE_GOLEM: B.Add(the_name + " starts moving. ", this); break; case ActorType.NECROMANCER: B.Add(TheVisible() + " starts chanting in low tones. "); break; case ActorType.TROLL: case ActorType.TROLL_SEER: B.Add(TheVisible() + " growls viciously. "); player.MakeNoise(); break; case ActorType.CARNIVOROUS_BRAMBLE: case ActorType.MIMIC: case ActorType.MUD_TENTACLE: case ActorType.MARBLE_HORROR: case ActorType.MARBLE_HORROR_STATUE: case ActorType.LASHER_FUNGUS: break; default: B.Add(the_name + " notices you. ", this); break; } Q1(); no_act = true; } } else { if (player_visibility_duration >= 0) { //if they hadn't seen the player yet... player_visibility_duration = 0; } else { if (target_location == null && player_visibility_duration-- == -(10 + attrs[Forays.AttrType.ALERTED] * 40)) { if (attrs[Forays.AttrType.ALERTED] < 2) { //they'll forget the player after 10 turns the first time and attrs[Forays.AttrType.ALERTED]++; //50 turns the second time, but that's the limit player_visibility_duration = 0; target = null; } } } } } if (atype == ActorType.MARBLE_HORROR && tile().IsLit()) { B.Add("The marble horror reverts to its statue form. ", this); atype = ActorType.MARBLE_HORROR_STATUE; SetName("marble horror statue"); attrs[Forays.AttrType.NEVER_MOVES] = 1; attrs[Forays.AttrType.INVULNERABLE] = 1; attrs[Forays.AttrType.IMMUNE_FIRE] = 1; } if (atype == ActorType.MARBLE_HORROR_STATUE && !tile().IsLit()) { B.Add("The marble horror animates once more. ", this); atype = ActorType.MARBLE_HORROR; SetName("marble horror"); attrs[Forays.AttrType.NEVER_MOVES] = 0; attrs[Forays.AttrType.INVULNERABLE] = 0; attrs[Forays.AttrType.IMMUNE_FIRE] = 0; } if (atype == ActorType.COMPY && group != null && target != null) { if (!group.Any(a => a.curhp < a.maxhp) && target.curhp >= 20 && !target.HasAttr(AttrType.ASLEEP) && !target.HasAttr(AttrType.PARALYZED) && !target.HasAttr(AttrType.IN_COMBAT)) { target = null; target_location = null; } } if (!no_act && atype != ActorType.CULTIST && atype != ActorType.CORPSETOWER_BEHEMOTH && atype != ActorType.BLOOD_MOTH && atype != ActorType.MUD_TENTACLE && atype != ActorType.DREAM_CLONE && atype != ActorType.ZOMBIE && atype != ActorType.CARNIVOROUS_BRAMBLE) { if (HasAttr(AttrType.HUMANOID_INTELLIGENCE)) { if (HasAttr(AttrType.CATCHING_FIRE) && Global.OneIn(10)) { attrs[AttrType.CATCHING_FIRE] = 0; B.Add(the_name + " stops the flames from spreading. ", this); Q1(); no_act = true; } else { if (HasAttr(AttrType.ON_FIRE)) { if (attrs[AttrType.ON_FIRE] == 1 && Global.OneIn(4)) { bool update = false; int oldradius = LightRadius(); if (attrs[AttrType.ON_FIRE] > light_radius) { update = true; } attrs[AttrType.ON_FIRE] = 0; if (update) { UpdateRadius(oldradius, LightRadius()); } B.Add(the_name + " puts out the fire. ", this); Q1(); no_act = true; } else { if (attrs[AttrType.ON_FIRE] > 1 && Global.Roll(10) <= 8) { bool update = false; int oldradius = LightRadius(); if (attrs[AttrType.ON_FIRE] > light_radius) { update = true; } int i = 2; if (Global.Roll(1, 3) == 3) { // 1 in 3 times, no progress against the fire i = 1; } attrs[AttrType.ON_FIRE] -= i; if (attrs[AttrType.ON_FIRE] < 0) { attrs[AttrType.ON_FIRE] = 0; } if (update) { UpdateRadius(oldradius, LightRadius()); } if (HasAttr(AttrType.ON_FIRE)) { B.Add(the_name + " puts out some of the fire. ", this); } else { B.Add(the_name + " puts out the fire. ", this); } Q1(); no_act = true; } else { if (attrs[AttrType.ON_FIRE] > 2 && Global.Roll(2) + attrs[AttrType.ON_FIRE] >= 5) { if (HasAttr(AttrType.MEDIUM_HUMANOID)) { B.Add(the_name + " runs around with arms flailing. ", this); } else { B.Add(the_name + " flails about. ", this); } await AI_Step(TileInDirection(Global.RandomDirection())); Q1(); no_act = true; } else { bool update = false; int oldradius = LightRadius(); if (attrs[AttrType.ON_FIRE] > light_radius) { update = true; } int i = 2; if (Global.Roll(1, 3) == 3) { // 1 in 3 times, no progress against the fire i = 1; } attrs[AttrType.ON_FIRE] -= i; if (attrs[AttrType.ON_FIRE] < 0) { attrs[AttrType.ON_FIRE] = 0; } if (update) { UpdateRadius(oldradius, LightRadius()); } if (HasAttr(AttrType.ON_FIRE)) { B.Add(the_name + " puts out some of the fire. ", this); } else { B.Add(the_name + " puts out the fire. ", this); } Q1(); no_act = true; } } } } } } else { if (HasAttr(AttrType.CATCHING_FIRE) && Global.CoinFlip()) { attrs[AttrType.CATCHING_FIRE] = 0; if (atype == ActorType.SHADOW) { B.Add(the_name + " reforms itself to stop the flames. ", this); } else { if (atype == ActorType.BANSHEE || atype == ActorType.VAMPIRE) { B.Add(the_name + " stops the flames from spreading. ", this); } else { B.Add(the_name + " rolls on the ground to stop the flames. ", this); } } Q1(); no_act = true; } else { if (HasAttr(AttrType.ON_FIRE) && Global.Roll(3) >= 2) { bool update = false; int oldradius = LightRadius(); if (attrs[AttrType.ON_FIRE] > light_radius) { update = true; } int i = 2; if (Global.Roll(1, 3) == 3) { // 1 in 3 times, no progress against the fire i = 1; } attrs[AttrType.ON_FIRE] -= i; if (attrs[AttrType.ON_FIRE] < 0) { attrs[AttrType.ON_FIRE] = 0; } if (update) { UpdateRadius(oldradius, LightRadius()); } if (HasAttr(AttrType.ON_FIRE)) { if (atype == ActorType.SHADOW) { B.Add(the_name + " reforms itself to put out some of the fire. ", this); } else { if (atype == ActorType.BANSHEE) { B.Add(the_name + " puts out some of the fire. ", this); } else { B.Add(the_name + " rolls on the ground to put out some of the fire. ", this); } } } else { if (atype == ActorType.SHADOW) { B.Add(the_name + " reforms itself to put out the fire. ", this); } else { if (atype == ActorType.BANSHEE) { B.Add(the_name + " puts out the fire. ", this); } else { B.Add(the_name + " rolls on the ground to put out the fire. ", this); } } } Q1(); no_act = true; } } } } if (tile().Is(FeatureType.QUICKFIRE) || tile().Is(FeatureType.POISON_GAS) || (HasAttr(AttrType.LIGHT_ALLERGY) && tile().IsLit())) { List<Tile> dangerous_terrain = new List<Tile>(); bool dangerous_terrain_here = false; if (HasAttr(AttrType.LIGHT_ALLERGY) && target == null) { //ignore this if the vampire sees the player already foreach (Tile t in TilesWithinDistance(1)) { if (t.IsLit() && t.passable) { dangerous_terrain.Add(t); if (t == tile()) { dangerous_terrain_here = true; } } } } if (!HasAttr(AttrType.IMMUNE_FIRE) && !HasAttr(AttrType.INVULNERABLE) && !HasAttr(AttrType.RESIST_FIRE)) { if (atype != ActorType.ZOMBIE && atype != ActorType.CORPSETOWER_BEHEMOTH && atype != ActorType.SKELETON && atype != ActorType.SKELETAL_SABERTOOTH && atype != ActorType.CULTIST && atype != ActorType.PHASE_SPIDER && atype != ActorType.MARBLE_HORROR && atype != ActorType.MECHANICAL_KNIGHT) { foreach (Tile t in TilesWithinDistance(1)) { if (t.Is(FeatureType.QUICKFIRE)) { dangerous_terrain.AddUnique(t); if (t == tile()) { dangerous_terrain_here = true; } } } } } if (!HasAttr(AttrType.IMMUNE_TOXINS) && !HasAttr(AttrType.UNDEAD) && !HasAttr(AttrType.CONSTRUCT)) { if (atype != ActorType.CULTIST && atype != ActorType.PHASE_SPIDER && atype != ActorType.MECHANICAL_KNIGHT) { foreach (Tile t in TilesWithinDistance(1)) { if (t.Is(FeatureType.POISON_GAS)) { dangerous_terrain.AddUnique(t); if (t == tile()) { dangerous_terrain_here = true; } } } } } if (dangerous_terrain_here) { /*if(target == null || DistanceFrom(target) > 1 || Global.CoinFlip()){ }*/ List<Tile> safe = new List<Tile>(); foreach (Tile t in TilesAtDistance(1)) { if (t.passable && t.actor() == null && !dangerous_terrain.Contains(t)) { safe.Add(t); } } if (safe.Count > 0) { if (await AI_Step(safe.Random())) { QS(); no_act = true; } } } } if (atype == ActorType.MECHANICAL_KNIGHT && !HasAttr(AttrType.COOLDOWN_1)) { attrs[Forays.AttrType.MECHANICAL_SHIELD] = 1; //if the knight was off balance, it regains its shield here. } if (group != null && group.Count == 0) { //this shouldn't happen, but does. this stops it from crashing. group = null; } if (!no_act) { if (target != null) { if (CanSee(target)) { await ActiveAI(); } else { await SeekAI(); } } else { await IdleAI(); } } if (atype == ActorType.DARKNESS_DWELLER) { if (HasAttr(AttrType.COOLDOWN_2)) { if (tile().IsLit()) { attrs[Forays.AttrType.COOLDOWN_2] = 5; } else { attrs[Forays.AttrType.COOLDOWN_2]--; } } else { if (tile().IsLit()) { B.Add(the_name + " is blinded by the light! ", this); attrs[Forays.AttrType.COOLDOWN_1]++; attrs[Forays.AttrType.COOLDOWN_2] = 5; Q.Add(new Event(this, (Global.Roll(2) + 4) * 100, AttrType.COOLDOWN_1, the_name + " is no longer blinded. ", new PhysicalObject[] { this })); } } } if (atype == ActorType.SHADOW) { CalculateDimming(); } }
public bool ConfirmsSafetyPrompts(Tile t) { if(CanSee(t) && !HasAttr(AttrType.CONFUSED)){ if(t.IsKnownTrap() && !HasAttr(AttrType.FLYING,AttrType.BRUTISH_STRENGTH) && !t.name.Contains("(safe)")){ if(!B.YesOrNoPrompt("Really step on " + t.TheName(true) + "?")){ return false; } } if(t.IsBurning() && !IsBurning() && !HasAttr(AttrType.SLIMED,AttrType.NONLIVING)){ //nonliving indicates stoneform if(!B.YesOrNoPrompt("Really walk into the fire?")){ return false; } } if(HasAttr(AttrType.OIL_COVERED) && !HasAttr(AttrType.IMMUNE_BURNING,AttrType.IMMUNE_FIRE)){ bool fire = false; foreach(Tile t2 in t.TilesWithinDistance(1)){ if((t2.actor() != null && t2.actor().IsBurning()) || t2.IsBurning()){ fire = true; break; } } if(fire && !B.YesOrNoPrompt("Really step next to the fire?")){ return false; } } if(t.Is(FeatureType.WEB) && !HasAttr(AttrType.SLIMED,AttrType.OIL_COVERED,AttrType.BRUTISH_STRENGTH) && !B.YesOrNoPrompt("Really walk into the web?")){ return false; } if(!HasAttr(AttrType.NONLIVING)){ if(t.Is(FeatureType.CONFUSION_GAS) && !B.YesOrNoPrompt("Really walk into the confusion gas?")){ return false; } if(t.Is(FeatureType.POISON_GAS) && !HasAttr(AttrType.POISONED) && !B.YesOrNoPrompt("Really walk into the poison gas?")){ return false; } if(t.Is(FeatureType.SPORES) && (!HasAttr(AttrType.POISONED) || !HasAttr(AttrType.STUNNED)) && !B.YesOrNoPrompt("Really walk into the spores?")){ return false; } if(t.Is(FeatureType.THICK_DUST) && !B.YesOrNoPrompt("Really walk into the cloud of dust?")){ return false; } } } return true; }
public async Task<bool> AI_Step(PhysicalObject obj, bool flee) { int rowchange = 0; int colchange = 0; if (obj.row < row) { rowchange = -1; } if (obj.row > row) { rowchange = 1; } if (obj.col < col) { colchange = -1; } if (obj.col > col) { colchange = 1; } if (flee) { rowchange = -rowchange; colchange = -colchange; } List<int> dirs = new List<int>(); if (rowchange == -1) { if (colchange == -1) { dirs.Add(7); } if (colchange == 0) { dirs.Add(8); } if (colchange == 1) { dirs.Add(9); } } if (rowchange == 0) { if (colchange == -1) { dirs.Add(4); } if (colchange == 1) { dirs.Add(6); } } if (rowchange == 1) { if (colchange == -1) { dirs.Add(1); } if (colchange == 0) { dirs.Add(2); } if (colchange == 1) { dirs.Add(3); } } if (dirs.Count == 0) { return true; } bool cw = Global.CoinFlip(); dirs.Add(RotateDirection(dirs[0], cw)); dirs.Add(RotateDirection(dirs[0], !cw)); //building a list of directions to try: first the primary direction, cw = Global.CoinFlip(); //then the ones next to it, then the ones next to THOSE(in random order) dirs.Add(RotateDirection(RotateDirection(dirs[0], cw), cw)); dirs.Add(RotateDirection(RotateDirection(dirs[0], !cw), !cw)); //this completes the list of 5 directions. foreach (int i in dirs) { if (ActorInDirection(i) != null && ActorInDirection(i).IsHiddenFrom(this)) { player_visibility_duration = -1; if (ActorInDirection(i) == player) { attrs[Forays.AttrType.PLAYER_NOTICED]++; } target = player; //not extensible yet target_location = M.tile[player.row, player.col]; string walks = " walks straight into you! "; if (HasAttr(AttrType.FLYING)) { walks = " flies straight into you! "; } if (!IsHiddenFrom(player)) { B.Add(TheVisible() + walks); if (player.CanSee(this)) { B.Add(the_name + " looks startled. "); } } else { attrs[AttrType.TURNS_VISIBLE] = -1; attrs[Forays.AttrType.NOTICED]++; B.Add(AVisible() + walks); if (player.CanSee(this)) { B.Add(the_name + " looks just as surprised as you. "); } } return true; } if (await AI_MoveOrOpen(i)) { return true; } } return false; }
public int GetDangerRating(Tile t) { //0 is no danger. 1 is minor danger, often ignored in favor of chasing the player. 2 is major danger, always avoided. if(HasAttr(AttrType.MINDLESS) || (type == ActorType.BERSERKER && HasAttr(AttrType.COOLDOWN_2)) || type == ActorType.FINAL_LEVEL_CULTIST){ return 0; } int dr = attrs[AttrType.DAMAGE_RESISTANCE]; int result = 0; if(HasAttr(AttrType.HUMANOID_INTELLIGENCE)){ if(!HasAttr(AttrType.NONLIVING,AttrType.PLANTLIKE)){ if(t.Is(FeatureType.CONFUSION_GAS) && !HasAttr(AttrType.MENTAL_IMMUNITY)){ return 2; } if(t.Is(FeatureType.SPORES) && (!HasAttr(AttrType.MENTAL_IMMUNITY) || dr < 2)){ if(HasAttr(AttrType.MENTAL_IMMUNITY,AttrType.STUNNED) && (HasAttr(AttrType.POISONED) || dr > 0)){ result = 1; } else{ return 2; } } if(t.Is(FeatureType.POISON_GAS) && type != ActorType.NOXIOUS_WORM && dr < 2){ if(HasAttr(AttrType.POISONED) || dr > 0){ result = 1; } else{ return 2; } } /*if(t.Is(TileType.POPPY_FIELD) && !HasAttr(AttrType.MAGICAL_DROWSINESS,AttrType.MENTAL_IMMUNITY)){ //todo int counter = attrs[AttrType.POPPY_COUNTER] + M.poppy_distance_map[t.p]; //todo: check for valid dijkstra int here? if(counter > 3){ return 2; } if(counter > 1){ result = 1; } }*/ if(t.Is(TileType.POPPY_FIELD) && !HasAttr(AttrType.MENTAL_IMMUNITY)){ if(M.poppy_distance_map[t.p] == 1){ //the outermost layer is considered less dangerous if(attrs[AttrType.POPPY_COUNTER] >= 3){ return 2; } else{ if(this != player){ //result = 1; } //result = 1; } } else{ if(M.poppy_distance_map[t.p].IsValidDijkstraValue()){ if(attrs[AttrType.POPPY_COUNTER] >= 2){ return 2; } else{ if(this != player){ result = 1; } //result = 1; } } } } } if(t.Is(FeatureType.THICK_DUST) && !HasAttr(AttrType.BLINDSIGHT,AttrType.PLANTLIKE)){ return 2; } foreach(Tile neighbor in t.TilesWithinDistance(1)){ if(neighbor.Is(FeatureType.GRENADE)){ return 2; } } } if(!HasAttr(AttrType.IMMUNE_FIRE) && dr < 3){ bool might_catch = false; int searing_tiles = 0; bool currently_flammable = (t.IsCurrentlyFlammable() || HasAttr(AttrType.OIL_COVERED)) && HasAttr(AttrType.HUMANOID_INTELLIGENCE); foreach(Tile neighbor in t.TilesWithinDistance(1)){ if(neighbor.IsBurning()){ searing_tiles++; if(currently_flammable){ might_catch = true; } } } if(dr == 0){ if(searing_tiles > 3){ return 2; } else{ if(searing_tiles > 0){ if(type == ActorType.ALASI_SCOUT && curhp == maxhp){ return 2; } result = 1; } } } if((might_catch || t.IsBurning()) && !HasAttr(AttrType.IMMUNE_BURNING)){ if(dr == 2){ result = 1; } else{ return 2; } } } return result; }
public async Task<bool> CastSpell(SpellType spell, PhysicalObject obj, bool force_of_will) { //returns false if targeting is canceled. if ((await StunnedThisTurn()) && !force_of_will) { //eventually this will be moved to the last possible second return true; //returns true because turn was used up. } if (!HasSpell(spell)) { return false; } foreach (Actor a in ActorsWithinDistance(2)) { if (a.HasAttr(AttrType.SPELL_DISRUPTION) && a.HasLOE(this)) { if (this == player) { if (CanSee(a)) { B.Add(a.Your() + " presence disrupts your spell! "); } else { B.Add("Something disrupts your spell! "); } } return false; } } Tile t = null; List<Tile> line = null; if (obj != null) { t = M.tile[obj.row, obj.col]; if (spell == SpellType.FORCE_BEAM) { //force beam requires a line for proper knockback line = GetBestExtendedLine(t); } else { line = GetBestLine(t); } } int bonus = 0; //used for bonus damage on spells - currently, only Master's Edge adds bonus damage. if (FailRate(spell) > 0) { int fail = FailRate(spell); if (force_of_will) { fail = magic_penalty * 5; fail -= skills[SkillType.SPIRIT] * 2; if (fail < 0) { fail = 0; } } if (Global.Roll(1, 100) - fail <= 0) { if (player.CanSee(this)) { B.Add("Sparks fly from " + Your() + " fingers. ", this); } else { if (player.DistanceFrom(this) <= 4 || (player.DistanceFrom(this) <= 12 && player.HasLOS(row, col))) { B.Add("You hear words of magic, but nothing happens. "); } } Q1(); return true; } } if (HasFeat(FeatType.MASTERS_EDGE)) { foreach (SpellType s in spells_in_order) { if (Spell.IsDamaging(s)) { if (s == spell) { bonus = 1; } break; } } } switch (spell) { case SpellType.SHINE: if (!HasAttr(AttrType.ENHANCED_TORCH)) { B.Add("You cast shine. "); if (!M.wiz_dark) { B.Add("Your torch begins to shine brightly. "); } attrs[AttrType.ENHANCED_TORCH]++; if (light_radius > 0) { UpdateRadius(LightRadius(), Global.MAX_LIGHT_RADIUS - attrs[AttrType.DIM_LIGHT] * 2, true); } Q.Add(new Event(9500, "Your torch begins to flicker a bit. ")); Q.Add(new Event(this, 10000, AttrType.ENHANCED_TORCH, "Your torch no longer shines as brightly. ")); } else { B.Add("Your torch is already shining brightly! "); return false; } break; /* case SpellType.MAGIC_MISSILE: if(t == null){ t = await GetTarget(); } if(t != null){ B.Add(You("cast") + " magic missile. ",this); Actor a = FirstActorInLine(t); if(a != null){ AnimateBoltProjectile(a,Color.Magenta); B.Add("The missile hits " + a.the_name + ". ",a); a.TakeDamage(DamageType.MAGIC,DamageClass.MAGICAL,Global.Roll(1+bonus,6),this); } else{ AnimateBoltProjectile(t,Color.Magenta); if(t.IsLit()){ B.Add("The missile hits " + t.the_name + ". "); } else{ B.Add("You attack the darkness. "); } } } else{ return false; } break; case SpellType.DETECT_MONSTERS: if(!HasAttr(AttrType.DETECTING_MONSTERS)){ B.Add(You("cast") + " detect monsters. ",this); if(type == ActorType.PLAYER){ B.Add("You can sense beings around you. "); Q.Add(new Event(this,2100,AttrType.DETECTING_MONSTERS,"You can no longer sense beings around you. ")); } else{ Q.Add(new Event(this,2100,AttrType.DETECTING_MONSTERS)); } attrs[AttrType.DETECTING_MONSTERS]++; } else{ B.Add("You are already detecting monsters! "); return false; } break;*/ case SpellType.IMMOLATE: if (t == null) { line = await GetTarget(12); if (line != null) { t = line.Last(); } } if (t != null) { B.Add(You("cast") + " immolate. ", this); Actor a = FirstActorInLine(line); if (a != null) { AnimateBeam(line.ToFirstObstruction(), "*", Color.RandomFire); if (!a.HasAttr(AttrType.RESIST_FIRE) && !a.HasAttr(AttrType.CATCHING_FIRE) && !a.HasAttr(AttrType.ON_FIRE)) { if (a.name == "you") { B.Add("You start to catch fire! "); } else { B.Add(a.the_name + " starts to catch fire. ", a); } a.attrs[AttrType.CATCHING_FIRE]++; } else { B.Add(a.You("shrug") + " off the flames. ", a); } } else { foreach (Tile t2 in line) { if (t2.Is(FeatureType.TROLL_CORPSE) || t2.Is(FeatureType.TROLL_SEER_CORPSE)) { line = line.To(t2); } } AnimateBeam(line, "*", Color.RandomFire); B.Add(You("throw") + " flames. ", this); if (line.Last().Is(FeatureType.TROLL_CORPSE)) { line.Last().features.Remove(FeatureType.TROLL_CORPSE); B.Add("The troll corpse burns to ashes! ", line.Last()); } if (line.Last().Is(FeatureType.TROLL_SEER_CORPSE)) { line.Last().features.Remove(FeatureType.TROLL_SEER_CORPSE); B.Add("The troll seer corpse burns to ashes! ", line.Last()); } } } else { return false; } break; case SpellType.FORCE_PALM: if (t == null) { t = TileInDirection(await GetDirection()); } if (t != null) { Actor a = M.actor[t.row, t.col]; B.Add(You("cast") + " force palm. ", this); //AnimateMapCell(t,Color.DarkCyan,"*"); B.DisplayNow(); Screen.AnimateMapCell(t.row, t.col, new colorchar("*", Color.Blue), 100); if (a != null) { B.Add(You("strike") + " " + a.TheVisible() + ". ", new PhysicalObject[] { this, a }); string s = a.the_name; string s2 = a.a_name; List<Tile> line2 = GetBestExtendedLine(a.row, a.col); int idx = line2.IndexOf(M.tile[a.row, a.col]); Tile next = line2[idx + 1]; await a.TakeDamage(DamageType.MAGIC, DamageClass.MAGICAL, Global.Roll(1 + bonus, 6), this, a_name); if (Global.Roll(1, 10) <= 7) { if (M.actor[t.row, t.col] != null) { await a.GetKnockedBack(this); } else { if (!next.passable) { B.Add(s + "'s corpse is knocked into " + next.the_name + ". ", new PhysicalObject[] { t, next }); } else { if (M.actor[next.row, next.col] != null) { B.Add(s + "'s corpse is knocked into " + M.actor[next.row, next.col].the_name + ". ", new PhysicalObject[] { t, M.actor[next.row, next.col] }); await M.actor[next.row, next.col].TakeDamage(DamageType.NORMAL, DamageClass.PHYSICAL, Global.Roll(1, 6), this, s2 + "'s falling corpse"); } } } } } else { if (t.passable) { B.Add("You strike at empty space. "); } else { B.Add("You strike " + t.the_name + " with your palm. "); if (t.ttype == TileType.DOOR_C) { //heh, why not? B.Add("It flies open! "); t.Toggle(this); } if (t.ttype == TileType.HIDDEN_DOOR) { //and this one gives it an actual use B.Add("A hidden door flies open! "); t.Toggle(this); t.Toggle(this); } } } } else { return false; } break; case SpellType.FREEZE: if (t == null) { line = await GetTarget(12); if (line != null) { t = line.Last(); } } if (t != null) { B.Add(You("cast") + " freeze. ", this); Actor a = FirstActorInLine(line); if (a != null) { AnimateBoltBeam(line.ToFirstObstruction(), Color.Cyan); if (!a.HasAttr(AttrType.FROZEN) && !a.HasAttr(AttrType.UNFROZEN)) { B.Add(a.YouAre() + " encased in ice. ", a); a.attrs[AttrType.FROZEN] = 25; } else { B.Add("The beam dissipates on the remaining ice. ", a); } } else { AnimateBoltBeam(line, Color.Cyan); B.Add("A bit of ice forms on " + t.the_name + ". ", t); } } else { return false; } break; case SpellType.BLINK: for (int i = 0; i < 9999; ++i) { int a = Global.Roll(1, 17) - 9; //-8 to 8 int b = Global.Roll(1, 17) - 9; if (Math.Abs(a) + Math.Abs(b) >= 6) { a += row; b += col; if (M.BoundsCheck(a, b) && M.tile[a, b].passable && M.actor[a, b] == null) { B.Add(You("cast") + " blink. ", this); B.Add(You("step") + " through a rip in reality. ", this); AnimateStorm(2, 3, 4, "*", Color.DarkMagenta); await Move(a, b); M.Draw(); AnimateStorm(2, 3, 4, "*", Color.DarkMagenta); break; } } } break; case SpellType.SCORCH: if (t == null) { line = await GetTarget(12); if (line != null) { t = line.Last(); } } if (t != null) { B.Add(You("cast") + " scorch. ", this); Actor a = FirstActorInLine(line); if (a != null) { AnimateProjectile(line.ToFirstObstruction(), "*", Color.RandomFire); B.Add("The scorching bolt hits " + a.the_name + ". ", a); await a.TakeDamage(DamageType.FIRE, DamageClass.MAGICAL, Global.Roll(2 + bonus, 6), this, a_name); } else { foreach (Tile t2 in line) { if (t2.Is(FeatureType.TROLL_CORPSE) || t2.Is(FeatureType.TROLL_SEER_CORPSE)) { line = line.To(t2); } } AnimateProjectile(line, "*", Color.RandomFire); B.Add("The scorching bolt hits " + t.the_name + ". ", t); if (line.Last().Is(FeatureType.TROLL_CORPSE)) { line.Last().features.Remove(FeatureType.TROLL_CORPSE); B.Add("The troll corpse burns to ashes! ", line.Last()); } if (line.Last().Is(FeatureType.TROLL_SEER_CORPSE)) { line.Last().features.Remove(FeatureType.TROLL_SEER_CORPSE); B.Add("The troll seer corpse burns to ashes! ", line.Last()); } } } else { return false; } break; case SpellType.BLOODSCENT: if (!HasAttr(AttrType.BLOODSCENT)) { B.Add(You("cast") + " bloodscent. ", this); attrs[Forays.AttrType.BLOODSCENT]++; if (atype == ActorType.PLAYER) { B.Add("You smell fear. "); Q.Add(new Event(this, 10000, Forays.AttrType.BLOODSCENT, "You lose the scent. ")); } else { Q.Add(new Event(this, 10000, Forays.AttrType.BLOODSCENT)); } } else { B.Add("You can already smell the blood of your enemies. "); return false; } break; case SpellType.LIGHTNING_BOLT: if (t == null) { line = await GetTarget(12); if (line != null) { t = line.Last(); } } if (t != null) { B.Add(You("cast") + " lightning bolt. ", this); PhysicalObject bolt_target = null; List<Actor> damage_targets = new List<Actor>(); foreach (Tile t2 in line) { if (t2.actor() != null && t2.actor() != this) { bolt_target = t2.actor(); damage_targets.Add(t2.actor()); break; } else { if (t2.ConductsElectricity()) { bolt_target = t2; break; } } } if (bolt_target != null) { Dict<PhysicalObject, List<PhysicalObject>> chain = new Dict<PhysicalObject, List<PhysicalObject>>(); chain[this] = new List<PhysicalObject> { bolt_target }; List<PhysicalObject> last_added = new List<PhysicalObject> { bolt_target }; for (bool done = false; !done; ) { done = true; List<PhysicalObject> new_last_added = new List<PhysicalObject>(); foreach (PhysicalObject added in last_added) { List<PhysicalObject> sort_list = new List<PhysicalObject>(); foreach (Tile nearby in added.TilesWithinDistance(3, true)) { if (nearby.actor() != null || nearby.ConductsElectricity()) { if (added.HasLOE(nearby)) { if (nearby.actor() != null) { bolt_target = nearby.actor(); } else { bolt_target = nearby; } bool contains_value = false; foreach (PhysicalObject k in chain.d.Keys) { List<PhysicalObject> list = chain.d[k]; foreach (PhysicalObject o in list) { if (o == bolt_target) { contains_value = true; break; } } if (contains_value) { break; } } if (!chain.d.ContainsKey(bolt_target) && !contains_value) { if (bolt_target as Actor != null) { damage_targets.AddUnique(bolt_target as Actor); } done = false; if (sort_list.Count == 0) { sort_list.Add(bolt_target); } else { int idx = 0; foreach (PhysicalObject o in sort_list) { if (bolt_target.DistanceFrom(added) < o.DistanceFrom(added)) { sort_list.Insert(idx, bolt_target); break; } ++idx; } if (idx == sort_list.Count) { sort_list.Add(bolt_target); } } if (chain[added] == null) { chain[added] = new List<PhysicalObject> { bolt_target }; } else { chain[added].Add(bolt_target); } } } } } foreach (PhysicalObject o in sort_list) { new_last_added.Add(o); } } if (!done) { last_added = new_last_added; } } //whew. the tree structure is complete. start at chain[this] and go from there... Dict<int, List<pos>> frames = new Dict<int, List<pos>>(); Dict<PhysicalObject, int> line_length = new Dict<PhysicalObject, int>(); line_length[this] = 0; List<PhysicalObject> current = new List<PhysicalObject> { this }; List<PhysicalObject> next = new List<PhysicalObject>(); while (current.Count > 0) { foreach (PhysicalObject o in current) { if (chain[o] != null) { foreach (PhysicalObject o2 in chain[o]) { List<Tile> bres = o.GetBestLine(o2); bres.RemoveAt(0); line_length[o2] = bres.Count + line_length[o]; int idx = 0; foreach (Tile t2 in bres) { if (frames[idx + line_length[o]] != null) { frames[idx + line_length[o]].Add(new pos(t2.row, t2.col)); } else { frames[idx + line_length[o]] = new List<pos> { new pos(t2.row, t2.col) }; } ++idx; } next.Add(o2); } } } current = next; next = new List<PhysicalObject>(); } List<pos> frame = frames[0]; for (int i = 0; frame != null; ++i) { foreach (pos p in frame) { Screen.WriteMapChar(p.row, p.col, "*", Color.RandomLightning); } await Task.Delay(50); frame = frames[i]; } foreach (Actor ac in damage_targets) { B.Add("The bolt hits " + ac.the_name + ". ", ac); await ac.TakeDamage(DamageType.ELECTRIC, DamageClass.MAGICAL, Global.Roll(2 + bonus, 6), this, a_name); } } else { AnimateBeam(line, "*", Color.RandomLightning); B.Add("The bolt hits " + t.the_name + ". ", t); } } else { return false; } break; case SpellType.SHADOWSIGHT: if (!HasAttr(AttrType.SHADOWSIGHT)) { B.Add("You cast shadowsight. "); B.Add("Your eyes pierce the darkness. "); int duration = 10001; GainAttr(AttrType.SHADOWSIGHT, duration, "You no longer see as well in darkness. "); GainAttr(AttrType.LOW_LIGHT_VISION, duration); } else { B.Add("Your eyes are already attuned to darkness. "); return false; } break; /*case SpellType.BURNING_HANDS: if(t == null){ t = TileInDirection(GetDirection()); } if(t != null){ B.Add(You("cast") + " burning hands. ",this); AnimateMapCell(t,Color.DarkRed,'*'); Actor a = M.actor[t.row,t.col]; if(a != null){ B.Add(You("project") + " flames onto " + a.the_name + ". ",this,a); a.TakeDamage(DamageType.FIRE,DamageClass.MAGICAL,Global.Roll(3+bonus,6),this); if(M.actor[t.row,t.col] != null && Global.Roll(1,10) <= 2){ B.Add(a.You("start") + " to catch fire! ",a); a.attrs[AttrType.CATCHING_FIRE]++; } } else{ B.Add("You project flames from your hands. "); } } else{ return false; } break; case SpellType.NIMBUS: { if(HasAttr(AttrType.NIMBUS_ON)){ B.Add("You're already surrounded by a nimbus. "); return false; } else{ B.Add(You("cast") + " nimbus. ",this); B.Add("An electric glow surrounds " + the_name + ". ",this); attrs[AttrType.NIMBUS_ON]++; int duration = (Global.Roll(5)+5)*100; Q.Add(new Event(this,duration,AttrType.NIMBUS_ON,"The electric glow fades from " + the_name + ". ",this)); } break; }*/ case SpellType.VOLTAIC_SURGE: { List<Actor> targets = new List<Actor>(); foreach (Actor a in ActorsWithinDistance(2, true)) { if (HasLOE(a)) { targets.Add(a); } } B.Add(You("cast") + " voltaic surge. ", this); AnimateExplosion(this, 2, Color.RandomLightning, "*"); if (targets.Count == 0) { B.Add("The air around " + the_name + " crackles. ", this); } else { while (targets.Count > 0) { Actor a = targets.Random(); targets.Remove(a); B.Add("Electricity blasts " + a.the_name + ". ", a); await a.TakeDamage(DamageType.ELECTRIC, DamageClass.MAGICAL, Global.Roll(3 + bonus, 6), this, a_name); } } break; } case SpellType.MAGIC_HAMMER: if (t == null) { t = TileInDirection(await GetDirection()); } if (t != null) { Actor a = t.actor(); B.Add(You("cast") + " magic hammer. ", this); B.DisplayNow(); Screen.AnimateMapCell(t.row, t.col, new colorchar("*", Color.Magenta), 100); if (a != null) { B.Add(You("smash", true) + " " + a.TheVisible() + ". ", this, a); if (await a.TakeDamage(DamageType.MAGIC, DamageClass.MAGICAL, Global.Roll(4 + bonus, 6), this, a_name)) { a.GainAttrRefreshDuration(AttrType.STUNNED, 201, a.YouAre() + " no longer stunned. ", a); B.Add(a.YouAre() + " stunned. ", a); } } else { B.Add("You smash " + t.the_name + ". "); } } else { return false; } break; case SpellType.RETREAT: //this is a player-only spell for now because it uses target_location to track position B.Add("You cast retreat. "); if (target_location == null) { target_location = M.tile[row, col]; B.Add("You create a rune of transport on " + M.tile[row, col].the_name + ". "); target_location.features.Add(FeatureType.RUNE_OF_RETREAT); } else { if (M.actor[target_location.row, target_location.col] == null && target_location.passable) { B.Add("You activate your rune of transport. "); await Move(target_location.row, target_location.col); target_location.features.Remove(FeatureType.RUNE_OF_RETREAT); target_location = null; } else { B.Add("Something blocks your transport. "); } } break; case SpellType.GLACIAL_BLAST: if (t == null) { line = await GetTarget(12); if (line != null) { t = line.Last(); } } if (t != null) { B.Add(You("cast") + " glacial blast. ", this); Actor a = FirstActorInLine(line); if (a != null) { AnimateProjectile(line.ToFirstObstruction(), "*", Color.RandomIce); B.Add("The glacial blast hits " + a.the_name + ". ", a); await a.TakeDamage(DamageType.COLD, DamageClass.MAGICAL, Global.Roll(3 + bonus, 6), this, a_name); } else { AnimateProjectile(line, "*", Color.RandomIce); B.Add("The glacial blast hits " + t.the_name + ". ", t); } } else { return false; } break; case SpellType.PASSAGE: { int i = DirectionOfOnlyUnblocked(TileType.WALL, true); if (i == 0) { B.Add("There's no wall here. ", this); return false; } else { if (t == null) { i = await GetDirection(true, false); t = TileInDirection(i); } else { i = DirectionOf(t); } if (t != null) { if (t.ttype == TileType.WALL) { B.Add(You("cast") + " passage. ", this); colorchar ch = new colorchar(Color.Cyan, "!"); if (this == player) { Game.Console.CursorVisible = false; switch (DirectionOf(t)) { case 8: case 2: ch.c = "|"; break; case 4: case 6: ch.c = "-"; break; } } List<Tile> tiles = new List<Tile>(); List<colorchar> memlist = new List<colorchar>(); while (!t.passable) { if (t.row == 0 || t.row == ROWS - 1 || t.col == 0 || t.col == COLS - 1) { break; } if (this == player) { tiles.Add(t); memlist.Add(Screen.MapChar(t.row, t.col)); Screen.WriteMapChar(t.row, t.col, ch); await Task.Delay(35); // Thread.Sleep(35); } t = t.TileInDirection(i); } if (t.passable && M.actor[t.row, t.col] == null) { if (this == player) { if (M.tile[row, col].inv != null) { Screen.WriteMapChar(row, col, new colorchar(tile().inv.color, tile().inv.symbol)); } else { Screen.WriteMapChar(row, col, new colorchar(tile().color, tile().symbol)); } Screen.WriteMapChar(t.row, t.col, new colorchar(color, symbol)); int j = 0; foreach (Tile tile in tiles) { Screen.WriteMapChar(tile.row, tile.col, memlist[j++]); await Task.Delay(35); //Thread.Sleep(35); } } await Move(t.row, t.col); M.Draw(); B.Add(You("travel") + " through the passage. ", this); } else { if (this == player) { int j = 0; foreach (Tile tile in tiles) { Screen.WriteMapChar(tile.row, tile.col, memlist[j++]); await Task.Delay(35); //Thread.Sleep(35); } B.Add("The passage is blocked. "); } } } else { if (this == player) { B.Add("There's no wall here. ", this); } return false; } } else { return false; } } break; } case SpellType.FLASHFIRE: if (t == null) { line = await GetTarget(12, 2); if (line != null) { t = line.Last(); } } if (t != null) { Actor a = FirstActorInLine(line); if (a != null) { t = a.tile(); } B.Add(You("cast") + " flashfire. ", this); AnimateBoltProjectile(line.ToFirstObstruction(), Color.Red); AnimateExplosion(t, 2, "*", Color.RandomFire); B.Add("Fwoosh! ", new PhysicalObject[] { this, t }); List<Actor> targets = new List<Actor>(); Tile prev = line.ToFirstObstruction()[line.ToFirstObstruction().Count - 2]; foreach (Actor ac in t.ActorsWithinDistance(2)) { if (t.passable) { if (t.HasBresenhamLine(ac.row, ac.col)) { targets.Add(ac); } } else { if (prev.HasBresenhamLine(ac.row, ac.col)) { targets.Add(ac); } } } foreach (Tile t2 in t.TilesWithinDistance(2)) { if (t.passable) { if (t.HasBresenhamLine(t2.row, t2.col)) { if (t2.actor() != null) { targets.Add(t2.actor()); } if (t2.Is(FeatureType.TROLL_CORPSE)) { t2.features.Remove(FeatureType.TROLL_CORPSE); B.Add("The troll corpse burns to ashes! ", t2); } if (t2.Is(FeatureType.TROLL_SEER_CORPSE)) { t2.features.Remove(FeatureType.TROLL_SEER_CORPSE); B.Add("The troll seer corpse burns to ashes! ", t2); } } } else { if (prev.HasBresenhamLine(t2.row, t2.col)) { if (t2.actor() != null) { targets.Add(t2.actor()); } if (t2.Is(FeatureType.TROLL_CORPSE)) { t2.features.Remove(FeatureType.TROLL_CORPSE); B.Add("The troll corpse burns to ashes! ", t2); } if (t2.Is(FeatureType.TROLL_SEER_CORPSE)) { t2.features.Remove(FeatureType.TROLL_SEER_CORPSE); B.Add("The troll seer corpse burns to ashes! ", t2); } } } } while (targets.Count > 0) { Actor ac = targets.RemoveRandom(); B.Add("The explosion hits " + ac.the_name + ". ", ac); await ac.TakeDamage(DamageType.FIRE, DamageClass.MAGICAL, Global.Roll(3 + bonus, 6), this, a_name); } } else { return false; } break; case SpellType.SONIC_BOOM: if (t == null) { line = await GetTarget(12); if (line != null) { t = line.Last(); } } if (t != null) { B.Add(You("cast") + " sonic boom. ", this); Actor a = FirstActorInLine(line); if (a != null) { AnimateProjectile(line.ToFirstObstruction(), "~", Color.Yellow); B.Add("A wave of sound hits " + a.the_name + ". ", a); int r = a.row; int c = a.col; await a.TakeDamage(DamageType.MAGIC, DamageClass.MAGICAL, Global.Roll(3 + bonus, 6), this, a_name); if (Global.Roll(1, 10) <= 5 && M.actor[r, c] != null && !M.actor[r, c].HasAttr(AttrType.STUNNED)) { B.Add(a.YouAre() + " stunned. ", a); a.attrs[AttrType.STUNNED]++; int duration = DurationOfMagicalEffect((Global.Roll(1, 4) + 2)) * 100; Q.Add(new Event(a, duration, AttrType.STUNNED, a.YouAre() + " no longer stunned. ", new PhysicalObject[] { a })); } } else { AnimateProjectile(line, "~", Color.Yellow); B.Add("Sonic boom! "); } } else { return false; } break; case SpellType.COLLAPSE: if (t == null) { line = await GetTarget(12, -1); if (line != null) { t = line.Last(); } } if (t != null) { B.Add(You("cast") + " collapse. ", this); B.DisplayNow(); for (int dist = 2; dist > 0; --dist) { List<pos> cells = new List<pos>(); List<colorchar> chars = new List<colorchar>(); pos p2 = new pos(t.row - dist, t.col - dist); if (p2.BoundsCheck()) { cells.Add(p2); chars.Add(new colorchar("\\", Color.DarkGreen)); } p2 = new pos(t.row - dist, t.col + dist); if (p2.BoundsCheck()) { cells.Add(p2); chars.Add(new colorchar("/", Color.DarkGreen)); } p2 = new pos(t.row + dist, t.col - dist); if (p2.BoundsCheck()) { cells.Add(p2); chars.Add(new colorchar("/", Color.DarkGreen)); } p2 = new pos(t.row + dist, t.col + dist); if (p2.BoundsCheck()) { cells.Add(p2); chars.Add(new colorchar("\\", Color.DarkGreen)); } Screen.AnimateMapCells(cells, chars); } Screen.AnimateMapCell(t.row, t.col, new colorchar("X", Color.DarkGreen)); Actor a = t.actor(); if (a != null) { B.Add("Part of the ceiling falls onto " + a.the_name + ". ", a); await a.TakeDamage(DamageType.BASHING, DamageClass.PHYSICAL, Global.Roll(4 + bonus, 6), this, a_name); } else { if (t.row == 0 || t.col == 0 || t.row == ROWS - 1 || t.col == COLS - 1) { B.Add("The wall resists. "); } else { if (t.ttype == TileType.WALL || t.ttype == TileType.HIDDEN_DOOR) { B.Add("The wall crashes down! "); t.TurnToFloor(); foreach (Tile neighbor in t.TilesAtDistance(1)) { if (neighbor.solid_rock) { neighbor.solid_rock = false; } } } } } List<Tile> open_spaces = new List<Tile>(); foreach (Tile neighbor in t.TilesWithinDistance(1)) { if (neighbor.passable) { if (a == null || neighbor != t) { //don't hit the same guy again open_spaces.Add(neighbor); } } } int count = 4; if (open_spaces.Count < 4) { count = open_spaces.Count; } for (; count > 0; --count) { Tile chosen = open_spaces.Random(); open_spaces.Remove(chosen); if (chosen.actor() != null) { B.Add("A rock falls onto " + chosen.actor().the_name + ". ", chosen.actor()); await chosen.actor().TakeDamage(DamageType.BASHING, Forays.DamageClass.PHYSICAL, Global.Roll(2, 6), this, a_name); } else { TileType prev = chosen.ttype; chosen.TransformTo(TileType.RUBBLE); chosen.toggles_into = prev; } } } else { return false; } break; case SpellType.FORCE_BEAM: if (t == null) { line = await GetTarget(); if (line != null) { t = line.Last(); } } if (t != null) { B.Add(You("cast") + " force beam. ", this); B.DisplayNow(); //List<Tile> line2 = GetBestExtendedLine(t.row,t.col); List<Tile> full_line = new List<Tile>(line); line = line.GetRange(0, Math.Min(13, line.Count)); for (int i = 0; i < 3; ++i) { //hits thrice Actor firstactor = null; Actor nextactor = null; Tile firsttile = null; Tile nexttile = null; foreach (Tile tile in line) { if (!tile.passable) { firsttile = tile; break; } if (M.actor[tile.row, tile.col] != null && M.actor[tile.row, tile.col] != this) { int idx = full_line.IndexOf(tile); firsttile = tile; firstactor = M.actor[tile.row, tile.col]; nexttile = full_line[idx + 1]; nextactor = M.actor[nexttile.row, nexttile.col]; break; } } AnimateBoltBeam(line.ToFirstObstruction(), Color.Cyan); if (firstactor != null) { string s = firstactor.TheVisible(); string s2 = firstactor.a_name; await firstactor.TakeDamage(DamageType.MAGIC, DamageClass.MAGICAL, Global.Roll(1 + bonus, 6), this, a_name); if (M.actor[firsttile.row, firsttile.col] != null) { await firstactor.GetKnockedBack(full_line); } else { if (!nexttile.passable) { B.Add(s + "'s corpse is knocked into " + nexttile.the_name + ". ", new PhysicalObject[] { firsttile, nexttile }); } else { if (nextactor != null) { B.Add(s + "'s corpse is knocked into " + nextactor.TheVisible() + ". ", new PhysicalObject[] { firsttile, nextactor }); await nextactor.TakeDamage(DamageType.NORMAL, DamageClass.PHYSICAL, Global.Roll(1, 6), this, s2 + "'s falling corpse"); } } } } } } else { return false; } break; /*case SpellType.DISINTEGRATE: if(t == null){ t = await GetTarget(); } if(t != null){ B.Add(You("cast") + " disintegrate. ",this); Actor a = FirstActorInLine(t); if(a != null){ AnimateBoltBeam(a,Color.DarkGreen); B.Add(You("direct") + " destructive energies toward " + a.the_name + ". ",this,a); a.TakeDamage(DamageType.MAGIC,DamageClass.MAGICAL,Global.Roll(8+bonus,6),this); } else{ AnimateBoltBeam(t,Color.DarkGreen); if(t.type == TileType.WALL || t.type == TileType.DOOR_C || t.type == TileType.DOOR_O || t.type == TileType.CHEST){ B.Add(You("direct") + " destructive energies toward " + t.the_name + ". ",this,t); B.Add(t.the_name + " turns to dust. ",t); t.TurnToFloor(); } } } else{ return false; } break;*/ case SpellType.AMNESIA: if (t == null) { t = TileInDirection(await GetDirection()); } if (t != null) { Actor a = t.actor(); if (a != null) { B.Add(You("cast") + " amnesia. ", this); /*for(int i=0;i<4;++i){ List<pos> cells = new List<pos>(); List<colorchar> chars = new List<colorchar>(); List<pos> nearby = a.p.PositionsWithinDistance(2); for(int j=0;j<4;++j){ cells.Add(nearby.RemoveRandom()); chars.Add(new colorchar('*',Color.RandomPrismatic)); } Screen.AnimateMapCells(cells,chars); }*/ a.AnimateStorm(2, 4, 4, "*", Color.RandomPrismatic); B.Add("You fade from " + a.TheVisible() + "'s awareness. "); a.player_visibility_duration = 0; a.target = null; a.target_location = null; a.attrs[Forays.AttrType.AMNESIA_STUN]++; } else { B.Add("There's nothing to target there. "); return false; } } else { return false; } break; case SpellType.BLIZZARD: { List<Actor> targets = ActorsWithinDistance(5, true); B.Add(You("cast") + " blizzard. ", this); AnimateStorm(5, 8, 24, "*", Color.RandomIce); B.Add("A massive ice storm surrounds " + the_name + ". ", this); while (targets.Count > 0) { int idx = Global.Roll(1, targets.Count) - 1; Actor a = targets[idx]; targets.Remove(a); B.Add("The blizzard hits " + a.the_name + ". ", a); int r = a.row; int c = a.col; await a.TakeDamage(DamageType.COLD, DamageClass.MAGICAL, Global.Roll(5 + bonus, 6), this, a_name); if (M.actor[r, c] != null && Global.Roll(1, 10) <= 8) { B.Add(a.the_name + " is encased in ice. ", a); a.attrs[AttrType.FROZEN] = 25; } } break; } case SpellType.BLESS: if (!HasAttr(AttrType.BLESSED)) { B.Add(You("cast") + " bless. ", this); B.Add(You("shine") + " briefly with inner light. ", this); attrs[AttrType.BLESSED]++; Q.Add(new Event(this, 400, AttrType.BLESSED)); } else { B.Add(YouAre() + " already blessed! ", this); return false; } break; case SpellType.MINOR_HEAL: B.Add(You("cast") + " minor heal. ", this); B.Add("A bluish glow surrounds " + the_name + ". ", this); await TakeDamage(DamageType.HEAL, DamageClass.NO_TYPE, Global.Roll(4, 6), null); break; case SpellType.HOLY_SHIELD: if (!HasAttr(AttrType.HOLY_SHIELDED)) { B.Add(You("cast") + " holy shield. ", this); B.Add("A fiery halo appears above " + the_name + ". ", this); attrs[AttrType.HOLY_SHIELDED]++; int duration = (Global.Roll(3, 2) + 1) * 100; Q.Add(new Event(this, duration, AttrType.HOLY_SHIELDED, the_name + "'s halo fades. ", new PhysicalObject[] { this })); } else { B.Add(Your() + " holy shield is already active. ", this); return false; } break; } if (atype == ActorType.PLAYER && spell != SpellType.AMNESIA) { MakeNoise(); } if (!force_of_will) { if (Spell.Level(spell) - TotalSkill(SkillType.MAGIC) > 0) { if (HasFeat(FeatType.STUDENTS_LUCK)) { if (Global.CoinFlip()) { magic_penalty++; B.Add(YouFeel() + " drained. ", this); } else { if (atype == ActorType.PLAYER) { B.Add("You feel lucky. "); //punk } } } else { magic_penalty++; B.Add(YouFeel() + " drained. ", this); } } } else { magic_penalty += 5; if (magic_penalty > 20) { magic_penalty = 20; } B.Add("You drain your magic reserves. "); } Q1(); return true; }
public void InputAI() { if(type == ActorType.DREAM_SPRITE && HasAttr(AttrType.COOLDOWN_2) && target != null){ bool no_los_needed = !target.CanSee(this); Tile t = target.TilesAtDistance(DistanceFrom(target)).Where(x=>x.passable && x.actor() == null && x.DistanceFrom(this) > 1 && (no_los_needed || target.CanSee(x))).RandomOrDefault(); if(t == null){ //gradually loosening the restrictions on placement... t = target.TilesAtDistance(DistanceFrom(target)).Where(x=>x.passable && x.actor() == null && (no_los_needed || target.CanSee(x))).RandomOrDefault(); } if(t == null){ t = target.TilesWithinDistance(12).Where(x=>x.passable && x.actor() == null && x.DistanceFrom(target) >= this.DistanceFrom(target) && x.DistanceFrom(this) > 1 && (no_los_needed || target.CanSee(x))).RandomOrDefault(); } if(t == null){ t = target.TilesWithinDistance(12).Where(x=>x.passable && x.actor() == null && x.DistanceFrom(target) >= this.DistanceFrom(target) && (no_los_needed || target.CanSee(x))).RandomOrDefault(); } if(t == null){ t = TilesAtDistance(2).Where(x=>x.passable && x.actor() == null && (no_los_needed || target.CanSee(x))).RandomOrDefault(); } if(t == null){ t = TilesWithinDistance(6).Where(x=>x.passable && x.actor() == null && (no_los_needed || target.CanSee(x))).RandomOrDefault(); } if(t == null){ t = M.AllTiles().Where(x=>x.passable && x.actor() == null && (no_los_needed || target.CanSee(x))).RandomOrDefault(); } if(t != null){ attrs[AttrType.COOLDOWN_2] = 0; if(group == null){ group = new List<Actor>{this}; } Actor clone = Create(ActorType.DREAM_SPRITE_CLONE,t.row,t.col,TiebreakerAssignment.InsertAfterCurrent); clone.speed = 100; bool seen = target.CanSee(clone); clone.player_visibility_duration = -1; group.Add(clone); clone.group = group; group.Randomize(); List<Tile> valid_tiles = new List<Tile>(); foreach(Actor a in group){ valid_tiles.Add(a.tile()); } Tile newtile = valid_tiles.Random(); if(newtile != tile()){ Move(newtile.row,newtile.col,false); } if(seen){ B.Add("Another " + name + " appears! "); } } } if(type == ActorType.MACHINE_OF_WAR){ attrs[AttrType.COOLDOWN_1]++; if(attrs[AttrType.COOLDOWN_1] == 16){ B.Add(the_name + " vents fire! ",this); if(HasAttr(AttrType.FROZEN)){ TakeDamage(DamageType.FIRE,DamageClass.PHYSICAL,1,null); } foreach(Tile t in TilesWithinDistance(1)){ if(t.actor() != null && t.actor() != this){ if(t.actor().TakeDamage(DamageType.FIRE,DamageClass.PHYSICAL,R.Roll(5,6),this,a_name)){ t.actor().ApplyBurning(); } } t.ApplyEffect(DamageType.FIRE); } attrs[AttrType.COOLDOWN_1] = 0; } } bool no_act = false; if(!no_act && HasAttr(AttrType.CONFUSED)){ ConfusedMove(); no_act = true; } if(!no_act && HasAttr(AttrType.BLIND)){ Stagger(); no_act = true; } if(!no_act && HasAttr(AttrType.ENRAGED)){ EnragedMove(); no_act = true; } bool aware_of_player = CanSee(player); if(HasAttr(AttrType.SEES_ADJACENT_PLAYER)){ if(DistanceFrom(player) == 1){ //this allows them to attack when the player is shadow cloaked aware_of_player = true; } else{ attrs[AttrType.SEES_ADJACENT_PLAYER] = 0; } } if(target == player && player.tile() == target_location && HasLOE(player)){ aware_of_player = true; } if(target == player && player_visibility_duration == -1 && DistanceFrom(player) == 1){ //the comparison to -1 exactly is what makes them lose track of the player after a turn of movement - i think. aware_of_player = true; } if(aware_of_player){ if(!Is(ActorType.BLADE) || target == null){ target = player; target_location = M.tile[player.row,player.col]; path.Clear(); } player_visibility_duration = -1; } else{ bool might_notice = false; if((IsWithinSightRangeOf(player.row,player.col) || (player.tile().IsLit() && !HasAttr(AttrType.BLINDSIGHT))) //if they're stealthed and nearby... && HasLOS(player.row,player.col) && (!player.IsInvisibleHere() || HasAttr(AttrType.BLINDSIGHT))){ //((removed player_noticed check from this line)) might_notice = true; } if(type == ActorType.CLOUD_ELEMENTAL){ List<pos> cloud = M.tile.GetFloodFillPositions(p,false,x=>M.tile[x].features.Contains(FeatureType.FOG)); foreach(pos p2 in cloud){ if(player.DistanceFrom(p2) <= 12){ if(M.tile[p2].HasLOS(player)){ might_notice = true; break; } } } } if(player.HasFeat(FeatType.CORNER_CLIMB) && DistanceFrom(player) > 1 && !player.tile().IsLit()){ List<pos> valid_open_doors = new List<pos>(); foreach(int dir in U.DiagonalDirections){ if(TileInDirection(dir).type == TileType.DOOR_O){ valid_open_doors.Add(TileInDirection(dir).p); } } if(SchismExtensionMethods.Extensions.ConsecutiveAdjacent(player.p,x=>valid_open_doors.Contains(x) || M.tile[x].Is(TileType.WALL,TileType.CRACKED_WALL,TileType.DOOR_C,TileType.HIDDEN_DOOR,TileType.STATUE,TileType.STONE_SLAB,TileType.WAX_WALL)) >= 5){ might_notice = false; } } if(type == ActorType.ORC_WARMAGE && HasAttr(AttrType.DETECTING_MOVEMENT) && player_visibility_duration >= 0 && !player.HasAttr(AttrType.TURNS_HERE) && DistanceFrom(player) <= 12){ might_notice = true; } if(!no_act && might_notice){ int multiplier = HasAttr(AttrType.KEEN_SENSES)? 5 : 10; //animals etc. are approximately twice as hard to sneak past int stealth = player.TotalSkill(SkillType.STEALTH); if(HasAttr(AttrType.BLINDSIGHT) && !player.tile().IsLit()){ //if this monster has blindsight, take away the stealth bonus for being in darkness stealth -= 2; } bool noticed = false; if(type == ActorType.ORC_WARMAGE && HasAttr(AttrType.DETECTING_MOVEMENT) && !player.HasAttr(AttrType.TURNS_HERE) && DistanceFrom(player) <= 12 && HasLOS(player)){ noticed = true; } if(stealth * DistanceFrom(player) * multiplier - player_visibility_duration++*5 < R.Roll(1,100)){ noticed = true; } if(noticed){ if(type == ActorType.BLADE){ if(target == null){ target = player; target_location = M.tile[player.row,player.col]; path.Clear(); } } else{ target = player; target_location = M.tile[player.row,player.col]; path.Clear(); PrintAggressionMessage(); Q1(); no_act = true; } player_visibility_duration = -1; attrs[AttrType.PLAYER_NOTICED]++; if(group != null){ foreach(Actor a in group){ if(a != this && DistanceFrom(a) < 3){ a.player_visibility_duration = -1; a.attrs[AttrType.PLAYER_NOTICED]++; a.target = player; a.target_location = M.tile[player.row,player.col]; } } } } } else{ if(player_visibility_duration >= 0){ //if they hadn't seen the player yet... player_visibility_duration = 0; } else{ int alerted = 1 + attrs[AttrType.ALERTED]; int turns_required = 10 * alerted * alerted; if(target_location == null && player_visibility_duration-- <= -turns_required){ attrs[AttrType.ALERTED]++; player_visibility_duration = 0; attrs[AttrType.AGGRESSION_MESSAGE_PRINTED] = 0; target = null; } } } } if(target != null && !HasAttr(AttrType.AGGRESSIVE) && curhp == maxhp){ if(HasAttr(AttrType.TERRITORIAL) && DistanceFrom(target) > 3){ target = null; target_location = null; } else{ if(DistanceFrom(target) > 12){ target = null; target_location = null; } } } if(type == ActorType.DARKNESS_DWELLER){ if((tile().IsLit() && tile().light_value > 0) || M.wiz_lite){ if(attrs[AttrType.COOLDOWN_1] < 7){ if(!HasAttr(AttrType.COOLDOWN_1) && player.HasLOS(this)){ B.Add(the_name + " is blinded by the light! ",this); } RefreshDuration(AttrType.BLIND,100,the_name + " can see again. ",this); attrs[AttrType.COOLDOWN_1]++; attrs[AttrType.COOLDOWN_2] = 7; } } else{ Event e = Q.FindAttrEvent(this,AttrType.BLIND); if(e == null || e.delay == 100){ RefreshDuration(AttrType.BLIND,0); } if(attrs[AttrType.COOLDOWN_2] > 0){ attrs[AttrType.COOLDOWN_2]--; if(!HasAttr(AttrType.COOLDOWN_2)){ attrs[AttrType.COOLDOWN_1] = 0; B.Add(Your() + " eyes adjust to the darkness. ",this); } } } } if(type == ActorType.MARBLE_HORROR && tile().IsLit()){ B.Add("The marble horror reverts to its statue form. ",this); type = ActorType.MARBLE_HORROR_STATUE; SetName("marble horror statue"); attrs[AttrType.IMMOBILE] = 1; attrs[AttrType.INVULNERABLE] = 1; if(HasAttr(AttrType.BURNING)){ RefreshDuration(AttrType.BURNING,0); } attrs[AttrType.IMMUNE_BURNING] = 1; if(curhp > 0){ curhp = maxhp; } } if(type == ActorType.MARBLE_HORROR_STATUE && !tile().IsLit()){ B.Add("The marble horror animates once more. ",this); type = ActorType.MARBLE_HORROR; SetName("marble horror"); attrs[AttrType.IMMOBILE] = 0; attrs[AttrType.INVULNERABLE] = 0; attrs[AttrType.IMMUNE_BURNING] = 0; } if(type == ActorType.CORPSETOWER_BEHEMOTH && tile().Is(TileType.FLOOR)){ tile().Toggle(null,TileType.GRAVE_DIRT); bool found = false; foreach(Event e in Q.list){ if(!e.dead && e.type == EventType.GRAVE_DIRT){ e.area.Add(tile()); found = true; break; } } if(!found){ Q.Add(new Event(new List<Tile>{tile()},100,EventType.GRAVE_DIRT)); } } int danger_threshold = (target != null? 1 : 0); if(!no_act && GetDangerRating(tile()) > danger_threshold){ if(HasAttr(AttrType.NONEUCLIDEAN_MOVEMENT) && target != null){ List<Tile> safest = target.TilesWithinDistance(DistanceFrom(target)+1).Where(x=>x.passable && x.actor() == null && x.DistanceFrom(target) >= DistanceFrom(target)-1).WhereLeast(x=>GetDangerRating(x)); if(CanSee(target)){ if(HasAttr(AttrType.KEEPS_DISTANCE)){ safest = safest.WhereGreatest(x=>x.DistanceFrom(target)); } else{ safest = safest.WhereLeast(x=>x.DistanceFrom(target)); } } if(safest.Count > 0){ Tile t = safest.Random(); Move(t.row,t.col); QS(); no_act = true; } } else{ List<Tile> safest = TilesWithinDistance(1).Where(x=>x.passable && x.actor() == null).WhereLeast(x=>GetDangerRating(x)); if(target != null && CanSee(target)){ if(HasAttr(AttrType.KEEPS_DISTANCE)){ safest = safest.WhereGreatest(x=>x.DistanceFrom(target)); } else{ safest = safest.WhereLeast(x=>x.DistanceFrom(target)); } } if(safest.Count > 0){ AI_Step(safest.Random()); QS(); no_act = true; } } } if(type == ActorType.MECHANICAL_KNIGHT && attrs[AttrType.COOLDOWN_1] != 1){ attrs[AttrType.MECHANICAL_SHIELD] = 1; //if the knight dropped its guard, it regains its shield here (unless it has no arms) } if(group != null && group.Count == 0){ //this shouldn't happen, but does. this stops it from crashing. group = null; } if(!no_act){ if(Is(ActorType.BLOOD_MOTH,ActorType.GHOST,ActorType.BLADE,ActorType.MINOR_DEMON,ActorType.FROST_DEMON,ActorType.BEAST_DEMON,ActorType.DEMON_LORD) || (type == ActorType.BERSERKER && HasAttr(AttrType.COOLDOWN_2))){ ActiveAI(); } else{ if(target != null){ if(CanSee(target) || (target == player && aware_of_player)){ ActiveAI(); } else{ SeekAI(); } } else{ IdleAI(); } } } if(type == ActorType.SHADOW){ CalculateDimming(); } if(type == ActorType.STALKING_WEBSTRIDER && !HasAttr(AttrType.BURROWING) && !tile().Is(FeatureType.WEB,FeatureType.FIRE)){ if(target != null && (CanSee(target) || target == player && aware_of_player)){ //not while wandering, just while chasing the player. tile().AddFeature(FeatureType.WEB); } } if(type == ActorType.CLOUD_ELEMENTAL){ List<Tile> area = new List<Tile>(); foreach(Tile t in TilesWithinDistance(1)){ if(t.passable){ t.AddFeature(FeatureType.FOG); area.Add(t); } } List<Tile> area2 = tile().AddGaseousFeature(FeatureType.FOG,2); area.AddRange(area2); if(area.Count > 0){ Q.RemoveTilesFromEventAreas(area,EventType.REMOVE_GAS); Event.RemoveGas(area,101,FeatureType.FOG,75); } } if(type == ActorType.NOXIOUS_WORM){ List<Tile> area = tile().AddGaseousFeature(FeatureType.POISON_GAS,2); if(area.Count > 0){ Q.RemoveTilesFromEventAreas(area,EventType.REMOVE_GAS); Event.RemoveGas(area,200,FeatureType.POISON_GAS,18); } } if(HasAttr(AttrType.SILENCE_AURA) && DistanceFrom(player) <= 2 && HasLOE(player)){ if(!player.HasAttr(AttrType.SILENCE_AURA_MESSAGE_COOLDOWN)){ if(player.CanSee(this)){ B.Add("Utter silence falls as " + the_name + " draws near. "); } else{ B.Add("Utter silence falls around you. "); } Help.TutorialTip(TutorialTopic.Silenced); } player.RefreshDuration(AttrType.SILENCE_AURA_MESSAGE_COOLDOWN,301); } }
public static void Targeting_RemoveLine(Tile tc,bool done,List<Tile> line,colorchar[,] mem,int radius) { if(done){ Screen.CursorVisible = false; foreach(Tile t in line){ Screen.WriteMapChar(t.row,t.col,mem[t.row,t.col]); } if(radius > 0){ foreach(Tile t in tc.TilesWithinDistance(radius,true)){ if(!line.Contains(t)){ Screen.WriteMapChar(t.row,t.col,mem[t.row,t.col]); } } } Screen.CursorVisible = true; } }
public Tile(Tile t,int r,int c){ ttype = t.ttype; name = t.name; a_name = t.a_name; the_name = t.the_name; symbol = t.symbol; color = t.color; passable = t.passable; opaque = t.opaque; seen = false; solid_rock = false; light_value = 0; toggles_into = t.toggles_into; inv = null; row = r; col = c; light_radius = t.light_radius; }
static void MainMenu() { ConsoleKeyInfo command; string recentname = "".PadRight(30); int recentdepth = -1; char recentwin = '-'; string recentcause = ""; bool on_highscore_list = false; MouseUI.PushButtonMap(); while(true){ Screen.Blank(); int row = 8; int col = (Global.SCREEN_W - 28) / 2; //centering "Forays into Norrendrin x.y.z", which is 28 chars. Screen.WriteString(row++,col,new cstr(Color.Yellow,"Forays into Norrendrin " + Global.VERSION)); Screen.WriteString(row++,col,new cstr(Color.Green,"".PadRight(28,'-'))); col += 4; //recenter for menu options row++; bool saved_game = File.Exists("forays.sav"); if(!saved_game){ Screen.WriteString(row++,col,"[a] Start a new game"); } else{ Screen.WriteString(row++,col,"[a] Resume saved game"); } Screen.WriteString(row++,col,"[b] How to play"); Screen.WriteString(row++,col,"[c] High scores"); Screen.WriteString(row++,col,"[d] Quit"); for(int i=0;i<4;++i){ Screen.WriteChar(i+row-4,col+1,new colorchar(Color.Cyan,(char)(i+'a'))); MouseUI.CreateButton((ConsoleKey)(i + ConsoleKey.A),false,i+row-4,0,1,Global.SCREEN_W); } Screen.ResetColors(); Screen.SetCursorPosition(Global.MAP_OFFSET_COLS,Global.MAP_OFFSET_ROWS+8); command = Input.ReadKey(); switch(command.KeyChar){ case 'a': { Global.GAME_OVER = false; Global.BOSS_KILLED = false; Global.SAVING = false; Global.LoadOptions(); Game game = new Game(); Actor.attack[ActorType.PLAYER] = new List<AttackInfo>{new AttackInfo(100,2,AttackEffect.NO_CRIT,"& hit *","& miss *","")}; if(!saved_game){ game.player = new Actor(ActorType.PLAYER,"you",'@',Color.White,100,100,0,0,AttrType.HUMANOID_INTELLIGENCE); game.player.inv = new List<Item>(); Actor.feats_in_order = new List<FeatType>(); Actor.spells_in_order = new List<SpellType>(); game.player.weapons.AddLast(new Weapon(WeaponType.SWORD)); game.player.weapons.AddLast(new Weapon(WeaponType.MACE)); game.player.weapons.AddLast(new Weapon(WeaponType.DAGGER)); game.player.weapons.AddLast(new Weapon(WeaponType.STAFF)); game.player.weapons.AddLast(new Weapon(WeaponType.BOW)); game.player.armors.AddLast(new Armor(ArmorType.LEATHER)); game.player.armors.AddLast(new Armor(ArmorType.CHAINMAIL)); game.player.armors.AddLast(new Armor(ArmorType.FULL_PLATE)); } game.M = new Map(game); game.B = new Buffer(game); game.Q = new Queue(game); Map.Q = game.Q; Map.B = game.B; PhysicalObject.M = game.M; PhysicalObject.B = game.B; PhysicalObject.Q = game.Q; PhysicalObject.player = game.player; Event.Q = game.Q; Event.B = game.B; Event.M = game.M; Event.player = game.player; Fire.fire_event = null; Fire.burning_objects = new List<PhysicalObject>(); if(!saved_game){ Actor.player_name = ""; if(File.Exists("name.txt")){ StreamReader file = new StreamReader("name.txt"); string base_name = file.ReadLine(); if(base_name == "%random%"){ Actor.player_name = Global.GenerateCharacterName(); } else{ Actor.player_name = base_name; } int num = 0; if(base_name != "%random%" && file.Peek() != -1){ num = Convert.ToInt32(file.ReadLine()); if(num > 1){ Actor.player_name = Actor.player_name + " " + Global.RomanNumeral(num); } } file.Close(); if(num > 0){ StreamWriter fileout = new StreamWriter("name.txt",false); fileout.WriteLine(base_name); fileout.WriteLine(num+1); fileout.Close(); } } if(Actor.player_name == ""){ MouseUI.PushButtonMap(MouseMode.NameEntry); Screen.Blank(); /*for(int i=4;i<=7;++i){ Screen.WriteMapString(i,0,"".PadToMapSize()); }*/ string s = ""; int name_option = 0; int c = 3; while(true){ Screen.WriteMapString(4,c,"Enter name: "); if(s == ""){ Screen.WriteMapString(6,c,"(Press [Enter] for a random name)".GetColorString()); } else{ Screen.WriteMapString(6,c,"(Press [Enter] when finished) ".GetColorString()); } List<string> name_options = new List<string>{"Default: Choose a new name for each character","Static: Use this name for every character","Legacy: Name all future characters after this one","Random: Name all future characters randomly"}; for(int i=0;i<4;++i){ Color option_color = Color.DarkGray; if(i == name_option){ option_color = Color.White; } Screen.WriteMapString(15+i,c,name_options[i],option_color); } Screen.WriteMapString(20,c,"(Press [Tab] to change naming preference)".GetColorString()); if(name_option != 0){ Screen.WriteMapString(22,c-5,"(To stop naming characters automatically, delete name.txt)",Color.Green); } else{ Screen.WriteMapString(22,c-5,"".PadToMapSize()); } Screen.WriteMapString(4,c+12,s.PadRight(26)); Screen.SetCursorPosition(c + Global.MAP_OFFSET_COLS + 12 + s.Length,Global.MAP_OFFSET_ROWS + 4); MouseUI.CreateButton(ConsoleKey.Enter,false,6+Global.MAP_OFFSET_ROWS,0,1,Global.SCREEN_W); MouseUI.CreateButton(ConsoleKey.Tab,false,20+Global.MAP_OFFSET_ROWS,0,1,Global.SCREEN_W); MouseUI.CreateButton(ConsoleKey.F21,false,15+Global.MAP_OFFSET_ROWS,0,1,Global.SCREEN_W); MouseUI.CreateButton(ConsoleKey.F22,false,16+Global.MAP_OFFSET_ROWS,0,1,Global.SCREEN_W); MouseUI.CreateButton(ConsoleKey.F23,false,17+Global.MAP_OFFSET_ROWS,0,1,Global.SCREEN_W); MouseUI.CreateButton(ConsoleKey.F24,false,18+Global.MAP_OFFSET_ROWS,0,1,Global.SCREEN_W); Screen.CursorVisible = true; command = Input.ReadKey(); if((command.KeyChar >= '!' && command.KeyChar <= '~') || command.KeyChar == ' '){ if(s.Length < 26){ s = s + command.KeyChar; } } else{ if(command.Key == ConsoleKey.Backspace && s.Length > 0){ s = s.Substring(0,s.Length-1); } else{ if(command.Key == ConsoleKey.Escape){ s = ""; } else{ if(command.Key == ConsoleKey.Tab){ name_option = (name_option + 1) % 4; } else{ if(command.Key == ConsoleKey.Enter){ if(s.Length == 0){ s = Global.GenerateCharacterName(); } else{ Actor.player_name = s; break; } } else{ switch(command.Key){ case ConsoleKey.F21: name_option = 0; break; case ConsoleKey.F22: name_option = 1; break; case ConsoleKey.F23: name_option = 2; break; case ConsoleKey.F24: name_option = 3; break; } } } } } } } MouseUI.PopButtonMap(); switch(name_option){ case 1: //static { StreamWriter fileout = new StreamWriter("name.txt",false); fileout.WriteLine(s); fileout.WriteLine(0); fileout.Close(); break; } case 2: //legacy { StreamWriter fileout = new StreamWriter("name.txt",false); fileout.WriteLine(s); fileout.WriteLine(2); fileout.Close(); break; } case 3: //random { StreamWriter fileout = new StreamWriter("name.txt",false); fileout.WriteLine("%random%"); fileout.WriteLine(0); fileout.Close(); break; } } } { Event e = new Event(game.player,0,EventType.MOVE); e.tiebreaker = 0; game.Q.Add(e); } Item.GenerateUnIDedNames(); game.M.GenerateLevelTypes(); game.M.GenerateLevel(); game.player.UpdateRadius(0,6,true); Item.Create(ConsumableType.BANDAGES,game.player).other_data = 5; Item.Create(ConsumableType.FLINT_AND_STEEL,game.player).other_data = 3; game.player.inv[0].revealed_by_light = true; game.player.inv[1].revealed_by_light = true; } else{ //loading FileStream file = new FileStream("forays.sav",FileMode.Open); BinaryReader b = new BinaryReader(file); Dictionary<int,PhysicalObject> id = new Dictionary<int, PhysicalObject>(); id.Add(0,null); Dict<PhysicalObject,int> missing_target_id = new Dict<PhysicalObject, int>(); List<Actor> need_targets = new List<Actor>(); Dict<PhysicalObject,int> missing_location_id = new Dict<PhysicalObject, int>(); List<Actor> need_location = new List<Actor>(); Actor.player_name = b.ReadString(); game.M.current_level = b.ReadInt32(); game.M.level_types = new List<LevelType>(); for(int i=0;i<20;++i){ game.M.level_types.Add((LevelType)b.ReadInt32()); } game.M.wiz_lite = b.ReadBoolean(); game.M.wiz_dark = b.ReadBoolean(); for(int i=0;i<Global.ROWS;++i){ for(int j=0;j<Global.COLS;++j){ game.M.last_seen[i,j].c = b.ReadChar(); game.M.last_seen[i,j].color = (Color)b.ReadInt32(); game.M.last_seen[i,j].bgcolor = (Color)b.ReadInt32(); } } if(game.M.current_level == 21){ game.M.final_level_cultist_count = new int[5]; for(int i=0;i<5;++i){ game.M.final_level_cultist_count[i] = b.ReadInt32(); } game.M.final_level_demon_count = b.ReadInt32(); game.M.final_level_clock = b.ReadInt32(); } Actor.feats_in_order = new List<FeatType>(); Actor.spells_in_order = new List<SpellType>(); int num_featlist = b.ReadInt32(); for(int i=0;i<num_featlist;++i){ Actor.feats_in_order.Add((FeatType)b.ReadInt32()); } int num_spelllist = b.ReadInt32(); for(int i=0;i<num_spelllist;++i){ Actor.spells_in_order.Add((SpellType)b.ReadInt32()); } int num_actor_tiebreakers = b.ReadInt32(); Actor.tiebreakers = new List<Actor>(num_actor_tiebreakers); for(int i=0;i<num_actor_tiebreakers;++i){ int ID = b.ReadInt32(); if(ID != 0){ Actor a = new Actor(); id.Add(ID,a); a.row = b.ReadInt32(); a.col = b.ReadInt32(); if(a.row >= 0 && a.row < Global.ROWS && a.col >= 0 && a.col < Global.COLS){ game.M.actor[a.row,a.col] = a; } Actor.tiebreakers.Add(a); a.name = b.ReadString(); a.the_name = b.ReadString(); a.a_name = b.ReadString(); a.symbol = b.ReadChar(); a.color = (Color)b.ReadInt32(); a.type = (ActorType)b.ReadInt32(); if(a.type == ActorType.PLAYER){ game.player = a; Actor.player = a; Buffer.player = a; Item.player = a; Map.player = a; Event.player = a; Tile.player = a; } a.maxhp = b.ReadInt32(); a.curhp = b.ReadInt32(); a.maxmp = b.ReadInt32(); a.curmp = b.ReadInt32(); a.speed = b.ReadInt32(); a.light_radius = b.ReadInt32(); int target_ID = b.ReadInt32(); if(id.ContainsKey(target_ID)){ a.target = (Actor)id[target_ID]; } else{ a.target = null; need_targets.Add(a); missing_target_id[a] = target_ID; } int num_items = b.ReadInt32(); for(int j=0;j<num_items;++j){ int item_id = b.ReadInt32(); if(item_id != 0){ Item item = new Item(); id.Add(item_id,item); item.name = b.ReadString(); item.the_name = b.ReadString(); item.a_name = b.ReadString(); item.symbol = b.ReadChar(); item.color = (Color)b.ReadInt32(); item.light_radius = b.ReadInt32(); item.type = (ConsumableType)b.ReadInt32(); item.quantity = b.ReadInt32(); item.charges = b.ReadInt32(); item.other_data = b.ReadInt32(); item.ignored = b.ReadBoolean(); item.do_not_stack = b.ReadBoolean(); item.revealed_by_light = b.ReadBoolean(); a.inv.Add(item); } } int num_attrs = b.ReadInt32(); for(int j=0;j<num_attrs;++j){ AttrType t = (AttrType)b.ReadInt32(); a.attrs[t] = b.ReadInt32(); } int num_skills = b.ReadInt32(); for(int j=0;j<num_skills;++j){ SkillType t = (SkillType)b.ReadInt32(); a.skills[t] = b.ReadInt32(); } int num_feats = b.ReadInt32(); for(int j=0;j<num_feats;++j){ FeatType t = (FeatType)b.ReadInt32(); a.feats[t] = b.ReadBoolean(); } int num_spells = b.ReadInt32(); for(int j=0;j<num_spells;++j){ SpellType t = (SpellType)b.ReadInt32(); a.spells[t] = b.ReadBoolean(); } a.exhaustion = b.ReadInt32(); a.time_of_last_action = b.ReadInt32(); a.recover_time = b.ReadInt32(); int path_count = b.ReadInt32(); for(int j=0;j<path_count;++j){ int path_row = b.ReadInt32(); int path_col = b.ReadInt32(); a.path.Add(new pos(path_row,path_col)); } int location_ID = b.ReadInt32(); if(id.ContainsKey(location_ID)){ a.target_location = (Tile)id[location_ID]; } else{ a.target_location = null; need_location.Add(a); missing_location_id[a] = location_ID; } a.player_visibility_duration = b.ReadInt32(); int num_weapons = b.ReadInt32(); for(int j=0;j<num_weapons;++j){ Weapon w = new Weapon(WeaponType.NO_WEAPON); w.type = (WeaponType)b.ReadInt32(); w.enchantment = (EnchantmentType)b.ReadInt32(); int num_statuses = b.ReadInt32(); for(int k=0;k<num_statuses;++k){ EquipmentStatus st = (EquipmentStatus)b.ReadInt32(); bool has_st = b.ReadBoolean(); w.status[st] = has_st; } a.weapons.AddLast(w); } int num_armors = b.ReadInt32(); for(int j=0;j<num_armors;++j){ Armor ar = new Armor(ArmorType.NO_ARMOR); ar.type = (ArmorType)b.ReadInt32(); ar.enchantment = (EnchantmentType)b.ReadInt32(); int num_statuses = b.ReadInt32(); for(int k=0;k<num_statuses;++k){ EquipmentStatus st = (EquipmentStatus)b.ReadInt32(); bool has_st = b.ReadBoolean(); ar.status[st] = has_st; } a.armors.AddLast(ar); } int num_magic_trinkets = b.ReadInt32(); for(int j=0;j<num_magic_trinkets;++j){ a.magic_trinkets.Add((MagicTrinketType)b.ReadInt32()); } } else{ Actor.tiebreakers.Add(null); } } int num_groups = b.ReadInt32(); for(int i=0;i<num_groups;++i){ List<Actor> group = new List<Actor>(); int group_size = b.ReadInt32(); for(int j=0;j<group_size;++j){ group.Add((Actor)id[b.ReadInt32()]); } foreach(Actor a in group){ a.group = group; } } int num_tiles = b.ReadInt32(); for(int i=0;i<num_tiles;++i){ Tile t = new Tile(); int ID = b.ReadInt32(); id.Add(ID,t); t.row = b.ReadInt32(); t.col = b.ReadInt32(); game.M.tile[t.row,t.col] = t; t.name = b.ReadString(); t.the_name = b.ReadString(); t.a_name = b.ReadString(); t.symbol = b.ReadChar(); t.color = (Color)b.ReadInt32(); t.light_radius = b.ReadInt32(); t.type = (TileType)b.ReadInt32(); t.passable = b.ReadBoolean(); t.SetInternalOpacity(b.ReadBoolean()); t.SetInternalSeen(b.ReadBoolean()); //t.seen = b.ReadBoolean(); t.revealed_by_light = b.ReadBoolean(); t.solid_rock = b.ReadBoolean(); t.light_value = b.ReadInt32(); t.direction_exited = b.ReadInt32(); if(b.ReadBoolean()){ //indicates a toggles_into value t.toggles_into = (TileType)b.ReadInt32(); } else{ t.toggles_into = null; } int item_id = b.ReadInt32(); if(item_id != 0){ t.inv = new Item(); id.Add(item_id,t.inv); t.inv.name = b.ReadString(); t.inv.the_name = b.ReadString(); t.inv.a_name = b.ReadString(); t.inv.symbol = b.ReadChar(); t.inv.color = (Color)b.ReadInt32(); t.inv.light_radius = b.ReadInt32(); t.inv.type = (ConsumableType)b.ReadInt32(); t.inv.quantity = b.ReadInt32(); t.inv.charges = b.ReadInt32(); t.inv.other_data = b.ReadInt32(); t.inv.ignored = b.ReadBoolean(); t.inv.do_not_stack = b.ReadBoolean(); t.inv.revealed_by_light = b.ReadBoolean(); } else{ t.inv = null; } int num_features = b.ReadInt32(); for(int j=0;j<num_features;++j){ t.features.Add((FeatureType)b.ReadInt32()); } } foreach(Actor a in need_targets){ if(id.ContainsKey(missing_target_id[a])){ a.target = (Actor)id[missing_target_id[a]]; } else{ throw new Exception("Error: some actors weren't loaded(1). "); } } foreach(Actor a in need_location){ if(id.ContainsKey(missing_location_id[a])){ a.target_location = (Tile)id[missing_location_id[a]]; } else{ throw new Exception("Error: some tiles weren't loaded(2). "); } } int game_turn = b.ReadInt32(); game.Q.turn = -1; //this keeps events from being added incorrectly to the front of the queue while loading. turn is set correctly after events are all loaded. int num_events = b.ReadInt32(); for(int i=0;i<num_events;++i){ Event e = new Event(); if(b.ReadBoolean()){ //if true, this is an item that doesn't exist elsewhere, so grab all its info. int item_id = b.ReadInt32(); if(item_id != 0){ Item item = new Item(); id.Add(item_id,item); item.name = b.ReadString(); item.the_name = b.ReadString(); item.a_name = b.ReadString(); item.symbol = b.ReadChar(); item.color = (Color)b.ReadInt32(); item.light_radius = b.ReadInt32(); item.type = (ConsumableType)b.ReadInt32(); item.quantity = b.ReadInt32(); item.charges = b.ReadInt32(); item.other_data = b.ReadInt32(); item.ignored = b.ReadBoolean(); item.do_not_stack = b.ReadBoolean(); item.revealed_by_light = b.ReadBoolean(); e.target = item; } } else{ int target_ID = b.ReadInt32(); if(id.ContainsKey(target_ID)){ e.target = id[target_ID]; } else{ throw new Exception("Error: some tiles/actors weren't loaded(4). "); } } int area_count = b.ReadInt32(); for(int j=0;j<area_count;++j){ if(e.area == null){ e.area = new List<Tile>(); } int tile_ID = b.ReadInt32(); if(id.ContainsKey(tile_ID)){ e.area.Add((Tile)id[tile_ID]); } else{ throw new Exception("Error: some tiles weren't loaded(5). "); } } e.delay = b.ReadInt32(); e.type = (EventType)b.ReadInt32(); e.attr = (AttrType)b.ReadInt32(); e.feature = (FeatureType)b.ReadInt32(); e.value = b.ReadInt32(); e.secondary_value = b.ReadInt32(); e.msg = b.ReadString(); int objs_count = b.ReadInt32(); for(int j=0;j<objs_count;++j){ if(e.msg_objs == null){ e.msg_objs = new List<PhysicalObject>(); } int obj_ID = b.ReadInt32(); if(id.ContainsKey(obj_ID)){ e.msg_objs.Add(id[obj_ID]); } else{ throw new Exception("Error: some actors/tiles weren't loaded(6). "); } } e.time_created = b.ReadInt32(); e.dead = b.ReadBoolean(); e.tiebreaker = b.ReadInt32(); game.Q.Add(e); if(e.type == EventType.FIRE && !e.dead){ Fire.fire_event = e; } } game.Q.turn = game_turn; foreach(Event e in game.Q.list){ if(e.type == EventType.MOVE && e.target == game.player){ game.Q.current_event = e; break; } } int num_footsteps = b.ReadInt32(); for(int i=0;i<num_footsteps;++i){ int step_row = b.ReadInt32(); int step_col = b.ReadInt32(); Actor.footsteps.Add(new pos(step_row,step_col)); } int num_prev_footsteps = b.ReadInt32(); for(int i=0;i<num_prev_footsteps;++i){ int step_row = b.ReadInt32(); int step_col = b.ReadInt32(); Actor.previous_footsteps.Add(new pos(step_row,step_col)); } Actor.interrupted_path.row = b.ReadInt32(); Actor.interrupted_path.col = b.ReadInt32(); UI.viewing_commands_idx = b.ReadInt32(); game.M.feat_gained_this_level = b.ReadBoolean(); game.M.extra_danger = b.ReadInt32(); int num_unIDed = b.ReadInt32(); for(int i=0;i<num_unIDed;++i){ ConsumableType ct = (ConsumableType)b.ReadInt32(); string s = b.ReadString(); Item.unIDed_name[ct] = s; } int num_IDed = b.ReadInt32(); for(int i=0;i<num_IDed;++i){ ConsumableType ct = (ConsumableType)b.ReadInt32(); bool IDed = b.ReadBoolean(); Item.identified[ct] = IDed; } int num_item_colors = b.ReadInt32(); for(int i=0;i<num_item_colors;++i){ ConsumableType ct = (ConsumableType)b.ReadInt32(); Item.proto[ct].color = (Color)b.ReadInt32(); } int num_burning = b.ReadInt32(); for(int i=0;i<num_burning;++i){ int obj_ID = b.ReadInt32(); if(id.ContainsKey(obj_ID)){ Fire.burning_objects.Add(id[obj_ID]); } else{ throw new Exception("Error: some actors/tiles weren't loaded(7). "); } } string[] messages = new string[Buffer.log_length]; int num_messages = b.ReadInt32(); for(int i=0;i<num_messages;++i){ messages[i] = b.ReadString(); } for(int i=num_messages;i<Buffer.log_length;++i){ messages[i] = ""; } int message_pos = b.ReadInt32(); game.B.LoadMessagesAndPosition(messages,message_pos,num_messages); b.Close(); file.Close(); File.Delete("forays.sav"); Tile.Feature(FeatureType.TELEPORTAL).color = Item.Prototype(ConsumableType.TELEPORTAL).color; game.M.CalculatePoppyDistanceMap(); game.M.UpdateDangerValues(); if(game.M.aesthetics == null) game.M.aesthetics = new PosArray<AestheticFeature>(Global.ROWS,Global.COLS); //todo! save these properly if(game.M.dungeon_description == null){ game.M.dungeon_description = new PosArray<string>(Global.ROWS,Global.COLS); for(int ii=0;ii<Global.ROWS;++ii){ for(int jj=0;jj<Global.COLS;++jj){ game.M.dungeon_description[ii,jj] = ""; } } } //todo fixme hack save properly } Game.NoClose = true; MouseUI.PushButtonMap(MouseMode.Map); MouseUI.CreateStatsButtons(); try{ while(!Global.GAME_OVER){ game.Q.Pop(); } } catch(Exception e){ StreamWriter fileout = new StreamWriter("error.txt",false); fileout.WriteLine(e.Message); fileout.WriteLine(e.StackTrace); fileout.Close(); MouseUI.IgnoreMouseMovement = true; MouseUI.IgnoreMouseClicks = true; Screen.CursorVisible = false; Screen.Blank(); Screen.WriteString(12,0," An error has occured. See error.txt for more details. Press any key to quit.".PadOuter(Global.SCREEN_W)); Input.ReadKey(); Global.Quit(); } MouseUI.PopButtonMap(); MouseUI.IgnoreMouseMovement = false; Game.NoClose = false; Screen.CursorVisible = false; Global.SaveOptions(); recentdepth = game.M.current_level; recentname = Actor.player_name; recentwin = Global.BOSS_KILLED? 'W' : '-'; recentcause = Global.KILLED_BY; on_highscore_list = false; if(!Global.SAVING){ List<string> newhighscores = new List<string>(); int num_scores = 0; bool added = false; if(File.Exists("highscore.txt")){ StreamReader file = new StreamReader("highscore.txt"); string s = ""; while(s.Length < 2 || s.Substring(0,2) != "--"){ s = file.ReadLine(); newhighscores.Add(s); } s = "!!"; while(s.Substring(0,2) != "--"){ s = file.ReadLine(); if(s.Substring(0,2) == "--"){ if(!added && num_scores < Global.HIGH_SCORES){ char symbol = Global.BOSS_KILLED? 'W' : '-'; newhighscores.Add(game.M.current_level.ToString() + " " + symbol + " " + Actor.player_name + " -- " + Global.KILLED_BY); on_highscore_list = true; } newhighscores.Add(s); break; } if(num_scores < Global.HIGH_SCORES){ string[] tokens = s.Split(' '); int dlev = Convert.ToInt32(tokens[0]); if(dlev < game.M.current_level || (dlev == game.M.current_level && Global.BOSS_KILLED)){ if(!added){ char symbol = Global.BOSS_KILLED? 'W' : '-'; newhighscores.Add(game.M.current_level.ToString() + " " + symbol + " " + Actor.player_name + " -- " + Global.KILLED_BY); ++num_scores; added = true; on_highscore_list = true; } if(num_scores < Global.HIGH_SCORES){ newhighscores.Add(s); ++num_scores; } } else{ newhighscores.Add(s); ++num_scores; } } } file.Close(); } else{ newhighscores.Add("High scores:"); newhighscores.Add("--"); char symbol = Global.BOSS_KILLED? 'W' : '-'; newhighscores.Add(game.M.current_level.ToString() + " " + symbol + " " + Actor.player_name + " -- " + Global.KILLED_BY); newhighscores.Add("--"); on_highscore_list = true; } StreamWriter fileout = new StreamWriter("highscore.txt",false); foreach(string str in newhighscores){ fileout.WriteLine(str); } fileout.Close(); } if(!Global.QUITTING && !Global.SAVING){ GameOverScreen(game); } break; } case 'b': { Help.DisplayHelp(); break; } case 'c': { MouseUI.PushButtonMap(); Screen.Blank(); List<string> scores = new List<string>(); { if(!File.Exists("highscore.txt")){ List<string> newhighscores = new List<string>(); newhighscores.Add("High scores:"); newhighscores.Add("--"); newhighscores.Add("--"); StreamWriter fileout = new StreamWriter("highscore.txt",false); foreach(string str in newhighscores){ fileout.WriteLine(str); } fileout.Close(); } StreamReader file = new StreamReader("highscore.txt"); string s = ""; while(s.Length < 2 || s.Substring(0,2) != "--"){ s = file.ReadLine(); } s = "!!"; while(s.Substring(0,2) != "--"){ s = file.ReadLine(); if(s.Substring(0,2) == "--" || scores.Count == Global.HIGH_SCORES){ break; } else{ scores.Add(s); } } file.Close(); } if(scores.Count == Global.HIGH_SCORES && !on_highscore_list && recentdepth != -1){ scores.RemoveLast(); scores.Add(recentdepth.ToString() + " " + recentwin + " " + recentname + " -- " + recentcause); } int longest_name = 0; int longest_cause = 0; foreach(string s in scores){ string[] tokens = s.Split(' '); string name_and_cause_of_death = s.Substring(tokens[0].Length + 3); int idx = name_and_cause_of_death.LastIndexOf(" -- "); string name = name_and_cause_of_death.Substring(0,idx); string cause_of_death = name_and_cause_of_death.Substring(idx+4); if(name.Length > longest_name){ longest_name = name.Length; } if(cause_of_death.Length > longest_cause){ longest_cause = cause_of_death.Length; } } int total_spaces = Global.SCREEN_W - (longest_name + 4 + longest_cause); //max name length is 26 and max cause length is 42. Depth is the '4'. int half_spaces = total_spaces / 2; int half_spaces_offset = (total_spaces+1) / 2; int spaces1 = half_spaces / 4; int spaces2 = half_spaces - (half_spaces / 4); int spaces3 = half_spaces_offset - (half_spaces_offset / 4); int name_middle = spaces1 + longest_name/2; int depth_middle = spaces1 + spaces2 + longest_name + 1; int cause_middle = spaces1 + spaces2 + spaces3 + longest_name + 4 + (longest_cause-1)/2; Color primary = Color.Green; Color recent = Color.Cyan; Screen.WriteString(0,(Global.SCREEN_W - 11) / 2,new cstr("HIGH SCORES",Color.Yellow)); //"HIGH SCORES" has width 11 Screen.WriteString(1,(Global.SCREEN_W - 11) / 2,new cstr("-----------",Color.Cyan)); Screen.WriteString(2,name_middle-4,new cstr("Character",primary)); Screen.WriteString(2,depth_middle-2,new cstr("Depth",primary)); Screen.WriteString(2,cause_middle-6,new cstr("Cause of death",primary)); bool written_recent = false; int line = 3; foreach(string s in scores){ if(line >= Global.SCREEN_H){ break; } string[] tokens = s.Split(' '); int dlev = Convert.ToInt32(tokens[0]); char winning = tokens[1][0]; string name_and_cause_of_death = s.Substring(tokens[0].Length + 3); int idx = name_and_cause_of_death.LastIndexOf(" -- "); string name = name_and_cause_of_death.Substring(0,idx); string cause_of_death = name_and_cause_of_death.Substring(idx+4); string cause_capitalized = cause_of_death.Substring(0,1).ToUpper() + cause_of_death.Substring(1); Color current_color = Color.White; if(!written_recent && name == recentname && dlev == recentdepth && winning == recentwin && cause_of_death == recentcause){ current_color = recent; written_recent = true; } else{ current_color = Color.White; } Screen.WriteString(line,spaces1,new cstr(name,current_color)); Screen.WriteString(line,spaces1 + spaces2 + longest_name,new cstr(dlev.ToString().PadLeft(2),current_color)); Screen.WriteString(line,spaces1 + spaces2 + spaces3 + longest_name + 4,new cstr(cause_capitalized,current_color)); if(winning == 'W'){ Screen.WriteString(line,spaces1 + spaces2 + longest_name + 3,new cstr("W",Color.Yellow)); } ++line; } Input.ReadKey(); MouseUI.PopButtonMap(); break; } case 'd': Global.Quit(); break; default: break; } if(Global.QUITTING){ Global.Quit(); } } }
public void ResetForNewLevel() { target = null; target_location = null; if (HasAttr(AttrType.DIM_LIGHT)) { attrs[AttrType.DIM_LIGHT] = 0; if (light_radius > 0) { if (HasAttr(AttrType.ENHANCED_TORCH)) { light_radius = 12; } else { light_radius = 6; } } } if (attrs[AttrType.RESTING] == -1) { attrs[AttrType.RESTING] = 0; } if (HasAttr(AttrType.GRABBED)) { attrs[AttrType.GRABBED] = 0; } ResetSpells(); Q.KillEvents(null, EventType.CHECK_FOR_HIDDEN); }
private static void Define(TileType type_,string name_,char symbol_,Color color_,bool passable_,bool opaque_,TileType? toggles_into_) { proto[type_] = new Tile(type_,name_,symbol_,color_,passable_,opaque_,toggles_into_); }
public Actor(Actor a, int r, int c) { atype = a.atype; name = a.name; the_name = a.the_name; a_name = a.a_name; symbol = a.symbol; color = a.color; maxhp = a.maxhp; curhp = maxhp; speed = a.speed; level = a.level; light_radius = a.light_radius; target = null; F = new SpellType[13]; for (int i = 0; i < 13; ++i) { F[i] = SpellType.NO_SPELL; } inv = new List<Item>(); row = r; col = c; target_location = null; time_of_last_action = 0; recover_time = 0; player_visibility_duration = 0; weapons = new List<WeaponType>(a.weapons); armors = new List<ArmorType>(a.armors); magic_items = new List<MagicItemType>(a.magic_items); attrs = new Dict<AttrType, int>(a.attrs); skills = new Dict<SkillType, int>(a.skills); feats = new Dict<FeatType, int>(a.feats); spells = new Dict<SpellType, int>(a.spells); magic_penalty = 0; }
public static bool Telekinesis(bool cast,Actor user,Tile t) { bool wand = !cast; if(t == null){ return false; } if(t != null){ if(wand && user == player && !Item.identified[ConsumableType.TELEKINESIS]){ Item.identified[ConsumableType.TELEKINESIS] = true; B.Add("(It was a wand of telekinesis!) "); B.PrintAll(); } List<Tile> ai_line = null; if(user != player && t == player.tile()){ var fires = user.TilesWithinDistance(12).Where(x=>x.passable && x.actor() == null && x.Is(FeatureType.FIRE) && user.CanSee(x) && player.HasBresenhamLineWithCondition(x,false,y=>y.passable && y.actor() == null)); if(fires.Count > 0){ ai_line = player.GetBestExtendedLineOfEffect(fires.Random()); } else{ if(wand){ ai_line = player.GetBestExtendedLineOfEffect(user); } else{ ai_line = player.GetBestExtendedLineOfEffect(player.TileInDirection(Global.RandomDirection())); } } } Actor a = t.actor(); if(a == null && t.inv == null && !t.Is(FeatureType.GRENADE) && t.Is(FeatureType.TROLL_CORPSE,FeatureType.TROLL_BLOODWITCH_CORPSE)){ ActorType troll_type = t.Is(FeatureType.TROLL_CORPSE)? ActorType.TROLL : ActorType.TROLL_BLOODWITCH; FeatureType troll_corpse = t.Is(FeatureType.TROLL_CORPSE)? FeatureType.TROLL_CORPSE : FeatureType.TROLL_BLOODWITCH_CORPSE; Event troll_event = Q.FindTargetedEvent(t,EventType.REGENERATING_FROM_DEATH); troll_event.dead = true; Actor troll = Actor.Create(troll_type,t.row,t.col); foreach(Event e in Q.list){ if(e.target == troll && e.type == EventType.MOVE){ e.tiebreaker = troll_event.tiebreaker; e.dead = true; break; } } Actor.tiebreakers[troll_event.tiebreaker] = troll; troll.symbol = '%'; troll.attrs[AttrType.CORPSE] = 1; troll.SetName(troll.name + "'s corpse"); troll.curhp = troll_event.value; troll.attrs[AttrType.PERMANENT_DAMAGE] = troll_event.secondary_value; troll.attrs[AttrType.NO_ITEM]++; t.features.Remove(troll_corpse); a = troll; } if(a != null){ string msg = "Throw " + a.TheName(true) + " in which direction? "; if(a == player){ msg = "Throw yourself in which direction? "; } List<Tile> line = null; if(user == player){ TargetInfo info = a.GetTarget(false,12,0,false,true,false,msg); if(info != null){ line = info.extended_line; } } else{ line = ai_line; } if(line != null){ if(line.Count == 1 && line[0].actor() == user){ if(wand){ return true; } return false; } if(cast){ B.Add(user.You("cast") + " telekinesis. ",user); if(a.type == ActorType.ALASI_BATTLEMAGE && !a.HasSpell(SpellType.TELEKINESIS)){ a.curmp += Spell.Tier(SpellType.TELEKINESIS); if(a.curmp > a.maxmp){ a.curmp = a.maxmp; } a.GainSpell(SpellType.TELEKINESIS); B.Add("Runes on " + a.Your() + " armor align themselves with the spell. ",a); } } if(a == user && a == player){ B.Add("You throw yourself forward. "); } else{ if(line.Count == 1){ B.Add(user.YouVisible("throw") + " " + a.TheName(true) + " into the ceiling. ",user,a); } else{ B.Add(user.YouVisible("throw") + " " + a.TheName(true) + ". ",user,a); } } B.DisplayNow(); user.attrs[AttrType.SELF_TK_NO_DAMAGE] = 1; a.attrs[AttrType.TELEKINETICALLY_THROWN] = 1; a.attrs[AttrType.TURN_INTO_CORPSE]++; if(line.Count == 1){ a.TakeDamage(DamageType.NORMAL,DamageClass.PHYSICAL,R.Roll(3,6),user,"colliding with the ceiling"); a.CollideWith(a.tile()); } else{ a.tile().KnockObjectBack(a,line,12,user); } a.attrs[AttrType.TELEKINETICALLY_THROWN] = 0; user.attrs[AttrType.SELF_TK_NO_DAMAGE] = 0; if(a.curhp <= 0 && a.HasAttr(AttrType.REGENERATES_FROM_DEATH)){ a.attrs[AttrType.TURN_INTO_CORPSE] = 0; a.attrs[AttrType.CORPSE] = 0; a.attrs[AttrType.FROZEN] = 0; a.attrs[AttrType.INVULNERABLE] = 0; a.attrs[AttrType.SHIELDED] = 0; a.attrs[AttrType.BLOCKING] = 0; a.curhp = 1; //this is all pretty hacky - perhaps I should relocate the regenerating corpse through other means. a.TakeDamage(DamageType.NORMAL,DamageClass.NO_TYPE,500,null); } else{ a.CorpseCleanup(); } } else{ if(wand){ return true; } return false; } } else{ bool blast_fungus = false; if(t.type == TileType.BLAST_FUNGUS && !t.Is(FeatureType.GRENADE,FeatureType.WEB,FeatureType.FORASECT_EGG,FeatureType.BONES)){ blast_fungus = true; } if(t.inv != null || blast_fungus){ Item i = t.inv; string itemname = ""; if(blast_fungus){ itemname = "the blast fungus"; } else{ itemname = i.TheName(true); if(i.quantity > 1){ itemname = "the " + i.SingularName(); } } string msg = "Throw " + itemname + " in which direction? "; List<Tile> line = null; if(user == player){ TargetInfo info = t.GetTarget(false,12,0,false,true,false,msg); if(info != null){ line = info.extended_line; } } else{ line = ai_line; } if(line != null){ if(line.Count > 13){ line = line.ToCount(13); //for range 12 } if(cast){ B.Add(user.You("cast") + " telekinesis. ",user); } if(blast_fungus){ B.Add("The blast fungus is pulled from the floor. ",t); B.Add("Its fuse ignites! ",t); t.Toggle(null); i = Item.Create(ConsumableType.BLAST_FUNGUS,t.row,t.col); if(i != null){ i.other_data = 3; i.revealed_by_light = true; Q.Add(new Event(i,100,EventType.BLAST_FUNGUS)); Screen.AnimateMapCell(t.row,t.col,new colorchar('3',Color.Red),100); } } if(line.Count == 1){ B.Add(user.YouVisible("throw") + " " + itemname + " into the ceiling. ",user,t); } else{ B.Add(user.YouVisible("throw") + " " + itemname + ". ",user,t); } B.DisplayNow(); if(i.quantity > 1){ i.quantity--; bool revealed = i.revealed_by_light; i = new Item(i,-1,-1); i.revealed_by_light = revealed; } else{ t.inv = null; Screen.WriteMapChar(t.row,t.col,M.VisibleColorChar(t.row,t.col)); } bool trigger_traps = false; Tile t2 = line.LastBeforeSolidTile(); Actor first = user.FirstActorInLine(line); if(t2 == line.LastOrDefault() && first == null){ trigger_traps = true; } if(first != null){ t2 = first.tile(); } line = line.ToFirstSolidTileOrActor(); //if(line.Count > 0){ // line.RemoveAt(line.Count - 1); //} if(line.Count > 0){ line.RemoveAt(line.Count - 1); } { Tile first_unseen = null; foreach(Tile tile2 in line){ if(!tile2.seen){ first_unseen = tile2; break; } } if(first_unseen != null){ line = line.To(first_unseen); if(line.Count > 0){ line.RemoveAt(line.Count - 1); } } } if(line.Count > 0){ //or > 1 ? user.AnimateProjectile(line,i.symbol,i.color); } if(first == user){ B.Add(user.You("catch",true) + " it! ",user); if(user.inv.Count < Global.MAX_INVENTORY_SIZE){ user.GetItem(i); } else{ B.Add("Your pack is too full to fit anything else. "); i.ignored = true; user.tile().GetItem(i); } } else{ if(first != null){ B.Add("It hits " + first.the_name + ". ",first); } if(i.IsBreakable()){ if(i.quantity > 1){ B.Add(i.TheName(true) + " break! ",t2); } else{ B.Add(i.TheName(true) + " breaks! ",t2); } if(i.NameOfItemType() == "orb"){ i.Use(null,new List<Tile>{t2}); } else{ i.CheckForMimic(); } } else{ t2.GetItem(i); } t2.MakeNoise(2); } if(first != null && first != user && first != player){ first.player_visibility_duration = -1; first.attrs[AttrType.PLAYER_NOTICED]++; } else{ if(trigger_traps && t2.IsTrap()){ t2.TriggerTrap(); } } } else{ if(wand){ return true; } return false; } } else{ if(!t.Is(FeatureType.GRENADE) && (t.Is(TileType.DOOR_C,TileType.DOOR_O,TileType.POISON_BULB) || t.Is(FeatureType.WEB,FeatureType.FORASECT_EGG,FeatureType.BONES))){ if(cast){ B.Add(user.You("cast") + " telekinesis. ",user); } if(t.Is(TileType.DOOR_C)){ B.Add("The door slams open. ",t); t.Toggle(null); } else{ if(t.Is(TileType.DOOR_O)){ B.Add("The door slams open. ",t); t.Toggle(null); } } if(t.Is(TileType.POISON_BULB)){ t.Bump(0); } if(t.Is(FeatureType.WEB)){ B.Add("The web is destroyed. ",t); t.RemoveFeature(FeatureType.WEB); } if(t.Is(FeatureType.FORASECT_EGG)){ B.Add("The egg is destroyed. ",t); t.RemoveFeature(FeatureType.FORASECT_EGG); //todo: forasect pathing? } if(t.Is(FeatureType.BONES)){ B.Add("The bones are scattered. ",t); t.RemoveFeature(FeatureType.BONES); } } else{ bool grenade = t.Is(FeatureType.GRENADE); bool barrel = t.Is(TileType.BARREL); bool flaming_barrel = barrel && t.IsBurning(); bool torch = t.Is(TileType.STANDING_TORCH); string feature_name = ""; int impact_damage_dice = 3; colorchar vis = new colorchar(t.symbol,t.color); switch(t.type){ case TileType.CRACKED_WALL: feature_name = "cracked wall"; break; case TileType.RUBBLE: feature_name = "pile of rubble"; break; case TileType.STATUE: feature_name = "statue"; break; } if(grenade){ impact_damage_dice = 0; feature_name = "grenade"; vis.c = ','; vis.color = Color.Red; } if(flaming_barrel){ feature_name = "flaming barrel of oil"; } if(barrel){ feature_name = "barrel"; } if(torch){ feature_name = "torch"; } if(feature_name == ""){ if(wand){ if(user == player){ B.Add("The wand grabs at empty space. ",t); } return true; } return false; } string msg = "Throw the " + feature_name + " in which direction? "; bool passable_hack = !t.passable; if(passable_hack){ t.passable = true; } List<Tile> line = null; if(user == player){ TargetInfo info = t.GetTarget(false,12,0,false,true,false,msg); if(info != null){ line = info.extended_line; } } else{ line = ai_line; } if(passable_hack){ t.passable = false; } if(line != null){ if(cast){ B.Add(user.You("cast") + " telekinesis. ",user); } if(line.Count == 1){ B.Add(user.YouVisible("throw") + " the " + feature_name + " into the ceiling. ",user,t); } else{ B.Add(user.YouVisible("throw") + " the " + feature_name + ". ",user,t); } B.DisplayNow(); user.attrs[AttrType.SELF_TK_NO_DAMAGE] = 1; switch(t.type){ case TileType.CRACKED_WALL: case TileType.RUBBLE: case TileType.STATUE: case TileType.BARREL: case TileType.STANDING_TORCH: if(flaming_barrel){ t.RemoveFeature(FeatureType.FIRE); } t.Toggle(null,TileType.FLOOR); foreach(Tile neighbor in t.TilesAtDistance(1)){ neighbor.solid_rock = false; } break; } if(grenade){ t.RemoveFeature(FeatureType.GRENADE); Event e = Q.FindTargetedEvent(t,EventType.GRENADE); if(e != null){ e.dead = true; } } Screen.WriteMapChar(t.row,t.col,M.VisibleColorChar(t.row,t.col)); colorchar[,] mem = Screen.GetCurrentMap(); int current_row = t.row; int current_col = t.col; // int knockback_strength = 12; if(line.Count == 1){ knockback_strength = 0; } int i=0; while(true){ Tile t2 = line[i]; if(t2 == t){ break; } ++i; } line.RemoveRange(0,i+1); while(knockback_strength > 0){ //if the knockback strength is greater than 1, you're passing *over* at least one tile. Tile t2 = line[0]; line.RemoveAt(0); if(!t2.passable){ if(t2.Is(TileType.CRACKED_WALL,TileType.DOOR_C,TileType.HIDDEN_DOOR) && impact_damage_dice > 0){ string tilename = t2.TheName(true); if(t2.type == TileType.HIDDEN_DOOR){ tilename = "a hidden door"; t2.Toggle(null); } B.Add("The " + feature_name + " flies into " + tilename + ". ",t2); t2.Toggle(null); Screen.WriteMapChar(current_row,current_col,mem[current_row,current_col]); } else{ B.Add("The " + feature_name + " flies into " + t2.TheName(true) + ". ",t2); if(impact_damage_dice > 0){ t2.Bump(M.tile[current_row,current_col].DirectionOf(t2)); } Screen.WriteMapChar(current_row,current_col,mem[current_row,current_col]); } knockback_strength = 0; break; } else{ if(t2.actor() != null){ B.Add("The " + feature_name + " flies into " + t2.actor().TheName(true) + ". ",t2); if(t2.actor().type != ActorType.SPORE_POD && !t2.actor().HasAttr(AttrType.SELF_TK_NO_DAMAGE)){ t2.actor().TakeDamage(DamageType.NORMAL,DamageClass.PHYSICAL,R.Roll(impact_damage_dice,6),user,"colliding with a " + feature_name); } knockback_strength = 0; Screen.WriteMapChar(t2.row,t2.col,vis); Screen.WriteMapChar(current_row,current_col,mem[current_row,current_col]); current_row = t2.row; current_col = t2.col; break; } else{ if(t2.Is(FeatureType.WEB)){ //unless perhaps grenades should get stuck and explode in the web? t2.RemoveFeature(FeatureType.WEB); } Screen.WriteMapChar(t2.row,t2.col,vis); Screen.WriteMapChar(current_row,current_col,mem[current_row,current_col]); current_row = t2.row; current_col = t2.col; Game.GLUpdate(); Thread.Sleep(20); } } //M.Draw(); knockback_strength--; } Tile current = M.tile[current_row,current_col]; if(grenade){ B.Add("The grenade explodes! ",current); current.ApplyExplosion(1,user,"an exploding grenade"); } if(barrel){ B.Add("The barrel smashes! ",current); List<Tile> cone = current.TilesWithinDistance(1).Where(x=>x.passable); List<Tile> added = new List<Tile>(); foreach(Tile t3 in cone){ foreach(int dir in U.FourDirections){ if(R.CoinFlip() && t3.TileInDirection(dir).passable){ added.AddUnique(t3.TileInDirection(dir)); } } } cone.AddRange(added); foreach(Tile t3 in cone){ t3.AddFeature(FeatureType.OIL); if(t3.actor() != null && !t3.actor().HasAttr(AttrType.OIL_COVERED,AttrType.SLIMED)){ if(t3.actor().IsBurning()){ t3.actor().ApplyBurning(); } else{ t3.actor().attrs[AttrType.OIL_COVERED] = 1; B.Add(t3.actor().YouAre() + " covered in oil. ",t3.actor()); if(t3.actor() == player){ Help.TutorialTip(TutorialTopic.Oiled); } } } } if(flaming_barrel){ current.ApplyEffect(DamageType.FIRE); } } if(torch){ current.AddFeature(FeatureType.FIRE); } user.attrs[AttrType.SELF_TK_NO_DAMAGE] = 0; } else{ if(wand){ return true; } return false; } } } } } else{ return false; } return true; }
public void SeekAI() { if(type == ActorType.MACHINE_OF_WAR && attrs[AttrType.COOLDOWN_1] % 2 == 1){ Q1(); return; } if(type == ActorType.ROBED_ZEALOT && HasAttr(AttrType.COOLDOWN_3)){ //todo: move most of these into the switch statement. use goto default. attrs[AttrType.COOLDOWN_3]--; Q1(); return; } if(type == ActorType.SKULKING_KILLER && HasAttr(AttrType.KEEPS_DISTANCE) && !R.OneIn(5) && (!player.HasLOE(this) || !player.CanSee(this) || DistanceFrom(player) > 12)){ attrs[AttrType.COOLDOWN_2]++; if(attrs[AttrType.COOLDOWN_2] >= 3){ attrs[AttrType.KEEPS_DISTANCE] = 0; attrs[AttrType.COOLDOWN_2] = 0; if(!player.CanSee(this)){ attrs[AttrType.TURNS_VISIBLE] = 0; } } Q1(); return; } if(type == ActorType.STALKING_WEBSTRIDER && tile().Is(FeatureType.WEB)){ List<pos> webs = M.tile.GetFloodFillPositions(p,false,x=>M.tile[x].Is(FeatureType.WEB)); if(webs.Contains(target.p)){ FindPath(target); if(PathStep()){ return; } else{ path.Clear(); } } } if(type == ActorType.SWORDSMAN || type == ActorType.PHANTOM_SWORDMASTER || type == ActorType.ALASI_SOLDIER){ attrs[AttrType.COMBO_ATTACK] = 0; } if(PathStep()){ //todo: consider the placement of this call. does this all happen in the correct order? return; } switch(type){ case ActorType.KOBOLD: { if(HasAttr(AttrType.COOLDOWN_1)){ //if the kobold needs to reload... if(target != null && DistanceFrom(target) <= 2){ //would be better as pathing distance, but oh well AI_Flee(); QS(); return; } else{ B.Add(the_name + " starts reloading. ",this); attrs[AttrType.COOLDOWN_1] = 0; Q1(); RefreshDuration(AttrType.COOLDOWN_2,R.Between(5,6)*100 - 50); return; } } else{ goto default; } } case ActorType.PHASE_SPIDER: case ActorType.IMPOSSIBLE_NIGHTMARE: if(DistanceFrom(target_location) <= 12){ Tile t = target_location.TilesAtDistance(DistanceFrom(target_location)-1).Where(x=>x.passable && x.actor() == null).RandomOrDefault(); if(t != null){ Move(t.row,t.col); } } QS(); break; case ActorType.ORC_WARMAGE: //warmages not following the player has worked pretty well so far. Maybe they could get a chance to go back to wandering? QS(); break; case ActorType.CARNIVOROUS_BRAMBLE: case ActorType.MUD_TENTACLE: case ActorType.LASHER_FUNGUS: case ActorType.MARBLE_HORROR_STATUE: QS(); break; case ActorType.CYCLOPEAN_TITAN: { bool smashed = false; if(target_location != null && target != null && DistanceFrom(target_location) == 1 && DistanceFrom(target) == 2 && !HasLOE(target)){ Tile t = FirstSolidTileInLine(target); if(t != null && !t.passable){ smashed = true; B.Add(You("smash",true) + " through " + t.TheName(true) + "! ",t); foreach(int dir in DirectionOf(t).GetArc(1)){ TileInDirection(dir).Smash(dir); } Move(t.row,t.col); QS(); } } if(!smashed){ goto default; } break; } case ActorType.FIRE_DRAKE: FindPath(player); QS(); break; default: { if(target_location != null){ if(DistanceFrom(target_location) == 1 && M.actor[target_location.p] != null){ if(MovementPrevented(target_location) || M.actor[target_location.p].MovementPrevented(tile()) || !AI_WillingToMove(tile(),target_location,target)){ QS(); } else{ Move(target_location.row,target_location.col); //swap places target_location = null; attrs[AttrType.FOLLOW_DIRECTION_EXITED]++; QS(); } } else{ int dist = DistanceFrom(target_location); if(!HasLOE(target_location)){ List<pos> path2 = GetPath(target_location,dist+1); if(path2.Count > 0){ path = path2; if(PathStep()){ return; } } } if(AI_Step(target_location)){ QS(); if(DistanceFrom(target_location) == 0){ target_location = null; attrs[AttrType.FOLLOW_DIRECTION_EXITED]++; } else{ if(DistanceFrom(target_location) == dist && !HasLOE(target_location)){ //if you didn't get any closer and you can't see it... target_location = null; } } } else{ //could not move, end turn. if((DistanceFrom(target_location) == 1 && !target_location.passable) || DistanceFrom(target_location) == 0){ target_location = null; } QS(); } } if(target_location == null){ if(!NeverWanders()){ if(group == null || group.Count < 2 || group[0] == this){ attrs[AttrType.WANDERING] = 1; } } } } else{ if(DistanceFrom(target) <= 2 && !HasLOS(target) && !HasLOE(target)){ //this part is just for pillar dancing List<pos> path2 = GetPath(target,2); if(path2.Count > 0){ path = path2; player_visibility_duration = -1; //stay at -1 while in close pursuit } if(PathStep()){ path.Clear(); //testing this; seems to be working. return; } QS(); } else{ if(HasAttr(AttrType.FOLLOW_DIRECTION_EXITED) && tile().direction_exited > 0){ AI_Step(TileInDirection(tile().direction_exited)); attrs[AttrType.FOLLOW_DIRECTION_EXITED] = 0; } else{ bool corridor = HasAttr(AttrType.DIRECTION_OF_PREVIOUS_TILE); //if it's 0 or -1, ignore it foreach(int dir in U.FourDirections){ if(TileInDirection(dir).passable && TileInDirection(dir.RotateDir(true,1)).passable && TileInDirection(dir.RotateDir(true,2)).passable){ corridor = false; break; } } if(corridor){ List<int> blocked = new List<int>(); for(int i=-1;i<=1;++i){ blocked.Add(attrs[AttrType.DIRECTION_OF_PREVIOUS_TILE].RotateDir(true,i)); } List<Tile> tiles = TilesAtDistance(1).Where(x=>x.passable && x.actor() == null && !blocked.Contains(DirectionOf(x))); if(tiles.Count > 0){ bool multiple_paths = false; foreach(Tile t1 in tiles){ foreach(Tile t2 in tiles){ if(t1 != t2 && t1.ApproximateEuclideanDistanceFromX10(t2) > 10){ //cardinally adjacent only multiple_paths = true; break; } } if(multiple_paths){ if(player_visibility_duration < 0){ player_visibility_duration -= 2; //at each fork in the road, the monster gets 3 steps closer to forgetting the player - just because 1 takes too long when the corridors are loopy. } break; } } if(!multiple_paths && player_visibility_duration < -1){ //this part could use some documentation. ++player_visibility_duration; } AI_Step(tiles.Random()); //this whole section deals with monsters following corridors instead of giving up on the chase. } } else{ if(group != null && group[0] != this){ //groups try to get back together if(DistanceFrom(group[0]) > 1){ int dir = DirectionOf(group[0]); bool found = false; for(int i=-1;i<=1;++i){ Actor a = ActorInDirection(dir.RotateDir(true,i)); if(a != null && group.Contains(a)){ found = true; break; } } if(!found){ if(HasLOS(group[0])){ AI_Step(group[0]); } else{ FindPath(group[0],8); if(PathStep()){ return; } } } } } } } QS(); } } break; } } }