├── .gitignore ├── ConsoleForays.sln ├── Forays.sln ├── Forays ├── Actor.cs ├── App.config ├── Buffer.cs ├── Color.cs ├── ConsoleForays.csproj ├── Effect.cs ├── Forays.csproj ├── ForaysHelp │ ├── advanced_help.txt │ ├── feat_help.txt │ ├── help.txt │ ├── item_help.txt │ └── spell_help.txt ├── ForaysImages │ ├── font10x20.png │ ├── font12x18.png │ ├── font12x24.png │ ├── font14x28.png │ ├── font15x27.png │ ├── font16x32.png │ ├── font18x36.png │ ├── font21x38.png │ ├── font6x12.png │ ├── font8x12.png │ ├── font8x16.png │ ├── forays.ico │ └── logo.png ├── ForaysUtility.cs ├── GL.cs ├── Global.cs ├── Help.cs ├── Input.cs ├── Item.cs ├── Main.cs ├── Map.cs ├── MouseUI.cs ├── Name.cs ├── PhysicalObject.cs ├── PosArray.cs ├── Properties │ └── AssemblyInfo.cs ├── Queue.cs ├── Schism.cs ├── Screen.cs ├── Spell.cs ├── Tile.cs ├── UI.cs ├── Utility.cs ├── highscore.txt ├── options.txt └── packages.config ├── ForaysUtilityTests ├── ForaysUtilityTests.csproj ├── Properties │ └── AssemblyInfo.cs ├── StringWrapBufferTest.cs └── packages.config ├── LICENSE.txt └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | x64/ 19 | x86/ 20 | build/ 21 | bld/ 22 | [Bb]in/ 23 | [Oo]bj/ 24 | 25 | # Visual Studio 2015 cache/options directory 26 | .vs/ 27 | # Uncomment if you have tasks that create the project's static files in wwwroot 28 | #wwwroot/ 29 | 30 | # MSTest test Results 31 | [Tt]est[Rr]esult*/ 32 | [Bb]uild[Ll]og.* 33 | 34 | # NUNIT 35 | *.VisualState.xml 36 | TestResult.xml 37 | 38 | # Build Results of an ATL Project 39 | [Dd]ebugPS/ 40 | [Rr]eleasePS/ 41 | dlldata.c 42 | 43 | # DNX 44 | project.lock.json 45 | artifacts/ 46 | 47 | *_i.c 48 | *_p.c 49 | *_i.h 50 | *.ilk 51 | *.meta 52 | *.obj 53 | *.pch 54 | *.pdb 55 | *.pgc 56 | *.pgd 57 | *.rsp 58 | *.sbr 59 | *.tlb 60 | *.tli 61 | *.tlh 62 | *.tmp 63 | *.tmp_proj 64 | *.log 65 | *.vspscc 66 | *.vssscc 67 | .builds 68 | *.pidb 69 | *.svclog 70 | *.scc 71 | 72 | # Chutzpah Test files 73 | _Chutzpah* 74 | 75 | # Visual C++ cache files 76 | ipch/ 77 | *.aps 78 | *.ncb 79 | *.opensdf 80 | *.sdf 81 | *.cachefile 82 | 83 | # Visual Studio profiler 84 | *.psess 85 | *.vsp 86 | *.vspx 87 | 88 | # TFS 2012 Local Workspace 89 | $tf/ 90 | 91 | # Guidance Automation Toolkit 92 | *.gpState 93 | 94 | # ReSharper is a .NET coding add-in 95 | _ReSharper*/ 96 | *.[Rr]e[Ss]harper 97 | *.DotSettings.user 98 | 99 | # JustCode is a .NET coding add-in 100 | .JustCode 101 | 102 | # TeamCity is a build add-in 103 | _TeamCity* 104 | 105 | # DotCover is a Code Coverage Tool 106 | *.dotCover 107 | 108 | # NCrunch 109 | _NCrunch_* 110 | .*crunch*.local.xml 111 | nCrunchTemp_* 112 | 113 | # MightyMoose 114 | *.mm.* 115 | AutoTest.Net/ 116 | 117 | # Web workbench (sass) 118 | .sass-cache/ 119 | 120 | # Installshield output folder 121 | [Ee]xpress/ 122 | 123 | # DocProject is a documentation generator add-in 124 | DocProject/buildhelp/ 125 | DocProject/Help/*.HxT 126 | DocProject/Help/*.HxC 127 | DocProject/Help/*.hhc 128 | DocProject/Help/*.hhk 129 | DocProject/Help/*.hhp 130 | DocProject/Help/Html2 131 | DocProject/Help/html 132 | 133 | # Click-Once directory 134 | publish/ 135 | 136 | # Publish Web Output 137 | *.[Pp]ublish.xml 138 | *.azurePubxml 139 | # TODO: Comment the next line if you want to checkin your web deploy settings 140 | # but database connection strings (with potential passwords) will be unencrypted 141 | *.pubxml 142 | *.publishproj 143 | 144 | # NuGet Packages 145 | *.nupkg 146 | # The packages folder can be ignored because of Package Restore 147 | **/packages/* 148 | # except build/, which is used as an MSBuild target. 149 | !**/packages/build/ 150 | # Uncomment if necessary however generally it will be regenerated when needed 151 | #!**/packages/repositories.config 152 | 153 | # Windows Azure Build Output 154 | csx/ 155 | *.build.csdef 156 | 157 | # Windows Store app package directory 158 | AppPackages/ 159 | 160 | # Visual Studio cache files 161 | # files ending in .cache can be ignored 162 | *.[Cc]ache 163 | # but keep track of directories ending in .cache 164 | !*.[Cc]ache/ 165 | 166 | # Others 167 | ClientBin/ 168 | [Ss]tyle[Cc]op.* 169 | ~$* 170 | *~ 171 | *.dbmdl 172 | *.dbproj.schemaview 173 | *.pfx 174 | *.publishsettings 175 | node_modules/ 176 | orleans.codegen.cs 177 | 178 | # RIA/Silverlight projects 179 | Generated_Code/ 180 | 181 | # Backup & report files from converting an old project file 182 | # to a newer Visual Studio version. Backup files are not needed, 183 | # because we have git ;-) 184 | _UpgradeReport_Files/ 185 | Backup*/ 186 | UpgradeLog*.XML 187 | UpgradeLog*.htm 188 | 189 | # SQL Server files 190 | *.mdf 191 | *.ldf 192 | 193 | # Business Intelligence projects 194 | *.rdl.data 195 | *.bim.layout 196 | *.bim_*.settings 197 | 198 | # Microsoft Fakes 199 | FakesAssemblies/ 200 | 201 | # Node.js Tools for Visual Studio 202 | .ntvs_analysis.dat 203 | 204 | # Visual Studio 6 build log 205 | *.plg 206 | 207 | # Visual Studio 6 workspace options file 208 | *.opt 209 | 210 | # Visual Studio LightSwitch build output 211 | **/*.HTMLClient/GeneratedArtifacts 212 | **/*.DesktopClient/GeneratedArtifacts 213 | **/*.DesktopClient/ModelManifest.xml 214 | **/*.Server/GeneratedArtifacts 215 | **/*.Server/ModelManifest.xml 216 | _Pvt_Extensions 217 | -------------------------------------------------------------------------------- /ConsoleForays.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 11.00 3 | # Visual Studio 2010 4 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ConsoleForays", "Forays\ConsoleForays.csproj", "{392222EF-9EEE-45F8-AFAE-260D9D06C4C9}" 5 | EndProject 6 | Global 7 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 8 | Debug|Any CPU = Debug|Any CPU 9 | Release|Any CPU = Release|Any CPU 10 | EndGlobalSection 11 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 12 | {392222EF-9EEE-45F8-AFAE-260D9D06C4C9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 13 | {392222EF-9EEE-45F8-AFAE-260D9D06C4C9}.Debug|Any CPU.Build.0 = Debug|Any CPU 14 | {392222EF-9EEE-45F8-AFAE-260D9D06C4C9}.Release|Any CPU.ActiveCfg = Release|Any CPU 15 | {392222EF-9EEE-45F8-AFAE-260D9D06C4C9}.Release|Any CPU.Build.0 = Release|Any CPU 16 | EndGlobalSection 17 | EndGlobal 18 | -------------------------------------------------------------------------------- /Forays.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 14 4 | VisualStudioVersion = 14.0.24720.0 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Forays", "Forays\Forays.csproj", "{392222EF-9EEE-45F8-AFAE-260D9D06C4C9}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ForaysUtilityTests", "ForaysUtilityTests\ForaysUtilityTests.csproj", "{90615D39-E969-4F26-A129-1D4EC7FE8577}" 9 | EndProject 10 | Global 11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 12 | Debug|Any CPU = Debug|Any CPU 13 | Release|Any CPU = Release|Any CPU 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {392222EF-9EEE-45F8-AFAE-260D9D06C4C9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 17 | {392222EF-9EEE-45F8-AFAE-260D9D06C4C9}.Debug|Any CPU.Build.0 = Debug|Any CPU 18 | {392222EF-9EEE-45F8-AFAE-260D9D06C4C9}.Release|Any CPU.ActiveCfg = Release|Any CPU 19 | {392222EF-9EEE-45F8-AFAE-260D9D06C4C9}.Release|Any CPU.Build.0 = Release|Any CPU 20 | {90615D39-E969-4F26-A129-1D4EC7FE8577}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 21 | {90615D39-E969-4F26-A129-1D4EC7FE8577}.Debug|Any CPU.Build.0 = Debug|Any CPU 22 | {90615D39-E969-4F26-A129-1D4EC7FE8577}.Release|Any CPU.ActiveCfg = Release|Any CPU 23 | {90615D39-E969-4F26-A129-1D4EC7FE8577}.Release|Any CPU.Build.0 = Release|Any CPU 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | EndGlobal 29 | -------------------------------------------------------------------------------- /Forays/App.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | -------------------------------------------------------------------------------- /Forays/Buffer.cs: -------------------------------------------------------------------------------- 1 | /*Copyright (c) 2011-2016 Derrick Creamer 2 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation 3 | files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, 4 | distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 7 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 8 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.*/ 9 | using System; 10 | using System.Collections.Generic; 11 | using Utilities; 12 | using ForaysUtilities; 13 | namespace Forays{ 14 | public enum Priority { Minor = -1, Normal = 0, Important = 1 }; 15 | public class MessageBuffer { //todo: move some of these fields and methods around for better organization. 16 | public MessageBuffer(Game g) { 17 | game = g; 18 | MessageVisibility = MessageVisibilityLevel.Default; 19 | MaxLength = Global.COLS; 20 | buffer = new StringWrapBuffer(NumLines,MaxLength,null,new char[] {' '}); 21 | buffer.ReservedSpace = more.Length; 22 | buffer.BufferFull += HandleOverflow; 23 | log = new List(); 24 | for(int i=0;i { "You can't move!","You're rooted to the ground!" }; //todo: is this the best place for these? 28 | } 29 | public Game game; 30 | public void Add(string message,params PhysicalObject[] objs) { Add(Priority.Normal,message,objs); } 31 | public void Add(Priority importance,string message,params PhysicalObject[] objs) { 32 | if(string.IsNullOrEmpty(message) 33 | || MessageVisibility == MessageVisibilityLevel.None 34 | || (MessageVisibility == MessageVisibilityLevel.ImportantOnly && importance != Priority.Important)) { 35 | return; 36 | } 37 | bool sightChecked = false; 38 | bool seen = false; 39 | if(objs != null) { 40 | foreach(PhysicalObject o in objs) { 41 | if(o != null) { 42 | sightChecked = true; 43 | if(game.player.CanSee(o)) { 44 | seen = true; 45 | break; 46 | } 47 | } 48 | } 49 | } 50 | if(seen || !sightChecked || MessageVisibility == MessageVisibilityLevel.All) { 51 | if(importance != Priority.Minor) interruptPlayer = true; 52 | buffer.Add(message.Capitalize()); 53 | if(importance == Priority.Important) Print(true); 54 | } 55 | } 56 | public void Print(bool requireMorePrompt) { 57 | if(requireMorePrompt) buffer.ConfirmReservedSpace(); 58 | DisplayLines(buffer.Clear(),requireMorePrompt,true); 59 | if(interruptPlayer) { 60 | game.player.Interrupt(); 61 | interruptPlayer = false; 62 | } 63 | } 64 | public void DisplayContents() { 65 | DisplayLines(buffer.Contents,false,false); 66 | } 67 | public List GetMessageLog() { return new List(log); } 68 | 69 | public string[] SaveMessages() { return null; } 70 | public int SavePosition() { return 0; } //todo: These 4 will be changed soon with a new serialization update. I'm leaving them broken until then. 71 | public int SaveNumMessages() { return 0; } 72 | public void LoadMessagesAndPosition(string[] s,int p,int num_msgs) { } 73 | 74 | public enum MessageVisibilityLevel { Default,All,ImportantOnly,None }; 75 | public MessageVisibilityLevel MessageVisibility; 76 | public List HideRepeatCountStrings; 77 | protected StringWrapBuffer buffer; 78 | protected List log; 79 | protected int repetitionCount; 80 | protected static readonly string more = " [more] "; 81 | protected const int NumLines = 3; 82 | protected readonly int MaxLength; 83 | protected bool interruptPlayer; 84 | protected void HandleOverflow(List lines) { 85 | DisplayLines(lines,true,true); 86 | //game.M.Draw(); //todo: necessary? and wouldn't it need to happen *before* the [more]? 87 | if(interruptPlayer) { 88 | game.player.Interrupt(); 89 | interruptPlayer = false; 90 | } 91 | } 92 | protected void DisplayLines(List lines,bool morePrompt,bool addToLog) { 93 | for(int i=0;i 0) { 97 | string last = GetPreviousMessage(0); 98 | string lastWithoutCount = last; 99 | if(repetitionCount > 0) { 100 | int repIdx = last.LastIndexOf(" (x" + (repetitionCount + 1) + ")"); 101 | if(repIdx != -1) { 102 | lastWithoutCount = last.Substring(0,repIdx); 103 | } 104 | } 105 | if(lines[0] == lastWithoutCount) { //if the new line matches the last one, verify that there's room for the (xN) 106 | repeated = true; 107 | if(HideRepeatCountStrings.Contains(lastWithoutCount)) { 108 | printCount = false; 109 | } 110 | else { 111 | int max = MaxLength; 112 | if(morePrompt) max -= more.Length; 113 | if((lastWithoutCount + " (x" + (repetitionCount + 2) + ")").Length > max) { 114 | repeated = false; 115 | } 116 | } 117 | } 118 | } 119 | int numPrev = NumLines - lines.Count; 120 | int prevStartIdx = numPrev - 1; 121 | if(repeated) prevStartIdx++; 122 | for(int i=0;i 156 | /// Get the nth-from-last message. (Therefore, 0 is the most recent.) 157 | /// 158 | protected string GetPreviousMessage(int n) { 159 | if(log.Count - 1 - n < 0) return ""; 160 | return log[log.Count - 1 - n]; 161 | } 162 | protected void AddToLog(IEnumerable lines) { 163 | foreach(string s in lines) log.Add(s); 164 | } 165 | protected static string RemoveTrailingSpaces(string s) { 166 | int idx = s.Length; 167 | for(int i=s.Length - 1;i>=0;--i) { 168 | if(s[i] == ' ') idx = i; 169 | else break; 170 | } 171 | if(idx == s.Length) return s; 172 | return s.Substring(0,idx); 173 | } 174 | } 175 | } 176 | 177 | -------------------------------------------------------------------------------- /Forays/Color.cs: -------------------------------------------------------------------------------- 1 | /*Copyright (c) 2015 Derrick Creamer 2 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation 3 | files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, 4 | distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 7 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 8 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.*/ 9 | using System; 10 | using OpenTK.Graphics; 11 | using Utilities; 12 | namespace Forays{ 13 | public enum Color{Black,White,Gray,Red,Green,Blue,Yellow,Magenta,Cyan,DarkGray,DarkRed,DarkGreen,DarkBlue,DarkYellow, 14 | DarkMagenta,DarkCyan,RandomFire,RandomIce,RandomLightning,RandomBreached,RandomExplosion,RandomGlowingFungus, 15 | RandomTorch,RandomDoom,RandomConfusion,RandomDark,RandomBright,RandomRGB,RandomDRGB,RandomRGBW,RandomCMY, 16 | RandomDCMY,RandomCMYW,RandomRainbow,RandomAny,OutOfSight,TerrainDarkGray,DarkerGray,HealthBar,StatusEffectBar, 17 | EnvironmentDescription,DarkEnvironmentDescription,DarkerRed,DarkerMagenta,ForestGreen,DarkForestGreen,Transparent}; 18 | public static class Colors{ 19 | public const Color darkcolor = Color.DarkCyan; //for darkened map objects 20 | public const Color unseencolor = Color.OutOfSight; 21 | public const Color status_highlight = Color.Green; 22 | public const Color status_darken = Color.DarkGray; 23 | public const Color status_highlight_darken = Color.DarkGreen; 24 | public static ConsoleColor GetColor(Color c){ 25 | switch(c){ 26 | case Color.Black: 27 | return ConsoleColor.Black; 28 | case Color.White: 29 | return ConsoleColor.White; 30 | case Color.Gray: 31 | return ConsoleColor.Gray; 32 | case Color.Red: 33 | return ConsoleColor.Red; 34 | case Color.Green: 35 | return ConsoleColor.Green; 36 | case Color.Blue: 37 | return ConsoleColor.Blue; 38 | case Color.Yellow: 39 | return ConsoleColor.Yellow; 40 | case Color.Magenta: 41 | return ConsoleColor.Magenta; 42 | case Color.Cyan: 43 | return ConsoleColor.Cyan; 44 | case Color.DarkGray: 45 | return ConsoleColor.DarkGray; 46 | case Color.DarkRed: 47 | return ConsoleColor.DarkRed; 48 | case Color.DarkGreen: 49 | return ConsoleColor.DarkGreen; 50 | case Color.DarkBlue: 51 | return ConsoleColor.DarkBlue; 52 | case Color.DarkYellow: 53 | return ConsoleColor.DarkYellow; 54 | case Color.DarkMagenta: 55 | return ConsoleColor.DarkMagenta; 56 | case Color.DarkCyan: 57 | return ConsoleColor.DarkCyan; 58 | case Color.RandomFire: 59 | case Color.RandomIce: 60 | case Color.RandomLightning: 61 | case Color.RandomBreached: 62 | case Color.RandomExplosion: 63 | case Color.RandomGlowingFungus: 64 | case Color.RandomTorch: 65 | case Color.RandomDoom: 66 | case Color.RandomConfusion: 67 | case Color.RandomDark: 68 | case Color.RandomBright: 69 | case Color.RandomRGB: 70 | case Color.RandomDRGB: 71 | case Color.RandomRGBW: 72 | case Color.RandomCMY: 73 | case Color.RandomDCMY: 74 | case Color.RandomCMYW: 75 | case Color.RandomRainbow: 76 | case Color.RandomAny: 77 | case Color.OutOfSight: 78 | case Color.TerrainDarkGray: 79 | case Color.HealthBar: 80 | case Color.StatusEffectBar: 81 | case Color.EnvironmentDescription: 82 | case Color.DarkEnvironmentDescription: 83 | return GetColor(ResolveColor(c)); 84 | default: 85 | return ConsoleColor.Black; 86 | } 87 | } 88 | public static Color ResolveColor(Color c){ 89 | switch(c){ 90 | case Color.RandomFire: 91 | return R.Choose(Color.Red,Color.DarkRed,Color.Yellow); 92 | case Color.RandomIce: 93 | return R.Choose(Color.White,Color.Cyan,Color.Blue,Color.DarkBlue); 94 | case Color.RandomLightning: 95 | return R.Choose(Color.White,Color.Yellow,Color.Yellow,Color.DarkYellow); 96 | case Color.RandomBreached: 97 | if(R.OneIn(4)){ 98 | return Color.DarkGreen; 99 | } 100 | return Color.Green; 101 | case Color.RandomExplosion: 102 | if(R.OneIn(4)){ 103 | return Color.Red; 104 | } 105 | return Color.DarkRed; 106 | case Color.RandomGlowingFungus: 107 | if(R.OneIn(35)){ 108 | return Color.DarkCyan; 109 | } 110 | return Color.Cyan; 111 | case Color.RandomTorch: 112 | if(R.OneIn(8)){ 113 | if(R.CoinFlip()){ 114 | return Color.White; 115 | } 116 | else{ 117 | return Color.Red; 118 | } 119 | } 120 | return Color.Yellow; 121 | case Color.RandomDoom: 122 | if(R.OneIn(6)){ 123 | return R.Choose(Color.DarkRed,Color.DarkGray); 124 | } 125 | return Color.DarkMagenta; 126 | //return R.Choose(Color.DarkGray,Color.DarkGray,Color.DarkRed,Color.DarkMagenta); 127 | case Color.RandomConfusion: 128 | if(R.OneIn(16)){ 129 | return R.Choose(Color.Red,Color.Green,Color.Blue,Color.Cyan,Color.Yellow,Color.White); 130 | } 131 | return Color.Magenta; 132 | case Color.RandomDark: 133 | return R.Choose(Color.DarkBlue,Color.DarkCyan,Color.DarkGray,Color.DarkGreen,Color.DarkMagenta,Color.DarkRed,Color.DarkYellow); 134 | case Color.RandomBright: 135 | return R.Choose(Color.Red,Color.Green,Color.Blue,Color.Cyan,Color.Yellow,Color.Magenta,Color.White,Color.Gray); 136 | case Color.RandomRGB: 137 | return R.Choose(Color.Red,Color.Green,Color.Blue); 138 | case Color.RandomDRGB: 139 | return R.Choose(Color.DarkRed,Color.DarkGreen,Color.DarkBlue); 140 | case Color.RandomRGBW: 141 | return R.Choose(Color.Red,Color.Green,Color.Blue,Color.White); 142 | case Color.RandomCMY: 143 | return R.Choose(Color.Cyan,Color.Magenta,Color.Yellow); 144 | case Color.RandomDCMY: 145 | return R.Choose(Color.DarkCyan,Color.DarkMagenta,Color.DarkYellow); 146 | case Color.RandomCMYW: 147 | return R.Choose(Color.Cyan,Color.Magenta,Color.Yellow,Color.White); 148 | case Color.RandomRainbow: 149 | return R.Choose(Color.Red,Color.Green,Color.Blue,Color.Cyan,Color.Yellow,Color.Magenta,Color.DarkBlue,Color.DarkCyan,Color.DarkGreen,Color.DarkMagenta,Color.DarkRed,Color.DarkYellow); 150 | case Color.RandomAny: 151 | return R.Choose(Color.Red,Color.Green,Color.Blue,Color.Cyan,Color.Yellow,Color.Magenta,Color.DarkBlue,Color.DarkCyan,Color.DarkGreen,Color.DarkMagenta,Color.DarkRed,Color.DarkYellow,Color.White,Color.Gray,Color.DarkGray); 152 | case Color.OutOfSight: 153 | if(Global.Option(OptionType.DARK_GRAY_UNSEEN)){ 154 | if(Screen.GLMode){ 155 | return Color.DarkerGray; 156 | } 157 | else{ 158 | return Color.DarkGray; 159 | } 160 | } 161 | else{ 162 | return Color.DarkBlue; 163 | } 164 | case Color.TerrainDarkGray: 165 | if(Screen.GLMode || !Global.Option(OptionType.DARK_GRAY_UNSEEN)){ 166 | return Color.DarkGray; 167 | } 168 | else{ 169 | return Color.Gray; 170 | } 171 | case Color.HealthBar: 172 | if(Screen.GLMode){ 173 | return Color.DarkerRed; 174 | } 175 | else{ 176 | return Color.DarkRed; 177 | } 178 | case Color.StatusEffectBar: 179 | if(Screen.GLMode){ 180 | return Color.DarkerMagenta; 181 | } 182 | else{ 183 | return Color.DarkMagenta; 184 | } 185 | case Color.EnvironmentDescription: 186 | if(Screen.GLMode){ 187 | return Color.ForestGreen; 188 | } 189 | else{ 190 | return Color.Green; 191 | } 192 | case Color.DarkEnvironmentDescription: 193 | if(Screen.GLMode){ 194 | return Color.DarkForestGreen; 195 | } 196 | else{ 197 | return Color.DarkGreen; 198 | } 199 | default: 200 | return c; 201 | } 202 | } 203 | public static Color4 ConvertColor(Color c){ 204 | switch(c){ 205 | case Color.Black: 206 | return Color4.Black; 207 | case Color.Blue: 208 | return new Color4(35,35,255,255); 209 | //return Color4.Blue; 210 | case Color.Cyan: 211 | return Color4.Cyan; 212 | case Color.DarkBlue: 213 | return new Color4(15,15,149,255); 214 | //return Color4.DarkBlue; 215 | case Color.DarkCyan: 216 | return Color4.DarkCyan; 217 | case Color.DarkGray: 218 | return Color4.DimGray; 219 | case Color.DarkGreen: 220 | return Color4.DarkGreen; 221 | case Color.DarkMagenta: 222 | return Color4.DarkMagenta; 223 | case Color.DarkRed: 224 | return Color4.DarkRed; 225 | case Color.DarkYellow: 226 | return Color4.DarkGoldenrod; 227 | case Color.Gray: 228 | return Color4.LightGray; 229 | case Color.Green: 230 | return Color4.Lime; 231 | case Color.Magenta: 232 | return Color4.Magenta; 233 | case Color.Red: 234 | return Color4.Red; 235 | case Color.White: 236 | return Color4.White; 237 | case Color.Yellow: 238 | return new Color4(255,248,0,255); 239 | //return Color4.Yellow; 240 | case Color.DarkerGray: 241 | return new Color4(50,50,50,255); 242 | case Color.DarkerRed: 243 | return new Color4(80,0,0,255); //DarkRed is 139 red 244 | case Color.DarkerMagenta: 245 | return new Color4(80,0,80,255); //DarkMagenta is 139 red and blue 246 | case Color.Transparent: 247 | return Color4.Transparent; 248 | case Color.ForestGreen: 249 | return new Color4(20,145,20,255); 250 | case Color.DarkForestGreen: 251 | return new Color4(10,80,10,255); 252 | default: 253 | return Color4.Black; 254 | } 255 | } 256 | } 257 | } 258 | -------------------------------------------------------------------------------- /Forays/ConsoleForays.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {392222EF-9EEE-45F8-AFAE-260D9D06C4C9} 8 | Exe 9 | Properties 10 | Forays 11 | ConsoleForays 12 | 512 13 | 14 | publish\ 15 | true 16 | Disk 17 | false 18 | Foreground 19 | 7 20 | Days 21 | false 22 | false 23 | true 24 | 0 25 | 1.0.0.%2a 26 | false 27 | false 28 | true 29 | False 30 | 8.0.30703 31 | 2.0 32 | 33 | 34 | true 35 | full 36 | false 37 | bin\Debug\ 38 | TRACE;DEBUG;CONSOLE 39 | prompt 40 | 4 41 | false 42 | true 43 | 44 | 45 | AnyCPU 46 | pdbonly 47 | true 48 | bin\Release\ 49 | TRACE;CONSOLE 50 | prompt 51 | 4 52 | 53 | 54 | Forays.Game 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | ..\packages\OpenTK.Next.1.1.1616.8959\lib\net20\OpenTK.dll 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | Always 101 | 102 | 103 | Always 104 | 105 | 106 | Always 107 | 108 | 109 | Always 110 | 111 | 112 | Always 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | Always 129 | 130 | 131 | Always 132 | 133 | 134 | 135 | 136 | False 137 | .NET Framework 3.5 SP1 Client Profile 138 | false 139 | 140 | 141 | False 142 | .NET Framework 3.5 SP1 143 | true 144 | 145 | 146 | 147 | 148 | 155 | -------------------------------------------------------------------------------- /Forays/Effect.cs: -------------------------------------------------------------------------------- 1 | /*Copyright (c) 2014-2016 Derrick Creamer 2 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation 3 | files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, 4 | distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 7 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 8 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.*/ 9 | using System; 10 | using System.Collections.Generic; 11 | using System.Threading; 12 | using PosArrays; 13 | using Utilities; 14 | using Nym; 15 | using static Nym.NameElement; 16 | namespace Forays{ 17 | public static class SharedEffect{ 18 | private static MessageBuffer B{ get{ return Actor.B; } } //todo: add collapse (and/or the falling stones effect) and passage here eventually 19 | private static Map M{ get{ return Actor.M; } } 20 | private static Queue Q{ get{ return Actor.Q; } } 21 | private static Actor player{ get{ return Actor.player; } } 22 | private static int ROWS{ get{ return Global.ROWS; } } 23 | private static int COLS{ get{ return Global.COLS; } } 24 | public static void ShowKnownItems(Hash IDed){ 25 | MouseUI.PushButtonMap(); 26 | UI.draw_bottom_commands = false; 27 | UI.darken_status_bar = true; 28 | const int width = 25; 29 | List potion_order = new List{ConsumableType.STONEFORM,ConsumableType.CLOAKING,ConsumableType.VAMPIRISM,ConsumableType.HEALING,ConsumableType.MYSTIC_MIND,ConsumableType.SILENCE,ConsumableType.REGENERATION,ConsumableType.ROOTS,ConsumableType.BRUTISH_STRENGTH,ConsumableType.HASTE}; 30 | List scroll_order = new List{ConsumableType.SUNLIGHT,ConsumableType.DARKNESS,ConsumableType.BLINKING,ConsumableType.RENEWAL,ConsumableType.FIRE_RING,ConsumableType.CALLING,ConsumableType.KNOWLEDGE,ConsumableType.PASSAGE,ConsumableType.THUNDERCLAP,ConsumableType.RAGE,ConsumableType.ENCHANTMENT,ConsumableType.TIME,ConsumableType.TRAP_CLEARING}; 31 | List orb_order = new List{ConsumableType.BREACHING,ConsumableType.FREEZING,ConsumableType.SHIELDING,ConsumableType.BLADES,ConsumableType.CONFUSION,ConsumableType.FLAMES,ConsumableType.DETONATION,ConsumableType.PAIN,ConsumableType.TELEPORTAL,ConsumableType.FOG}; 32 | List wand_order = new List{ConsumableType.DUST_STORM,ConsumableType.SLUMBER,ConsumableType.TELEKINESIS,ConsumableType.REACH,ConsumableType.INVISIBILITY,ConsumableType.WEBS,ConsumableType.FLESH_TO_FIRE}; 33 | List potions = new List(); 34 | List scrolls = new List(); 35 | List orbs = new List(); 36 | List wands = new List(); 37 | List> string_lists = new List>{potions,scrolls,orbs,wands}; 38 | int list_idx = 0; 39 | foreach(List item_list in new List>{potion_order,scroll_order,orb_order,wand_order}){ 40 | int item_idx = 0; 41 | while(item_idx + 1 < item_list.Count){ 42 | ConsumableType[] ct = new ConsumableType[2]; 43 | string[] names = new string[2]; 44 | Color[] ided_color = new Color[2]; 45 | for(int i=0;i<2;++i){ 46 | ct[i] = item_list[item_idx + i]; 47 | names[i] = ct[i].ToString()[0] + ct[i].ToString().Substring(1).ToLower(); 48 | names[i] = names[i].Replace('_',' '); 49 | if(IDed[ct[i]]){ 50 | ided_color[i] = Color.Cyan; 51 | } 52 | else{ 53 | ided_color[i] = Color.DarkGray; 54 | } 55 | } 56 | int num_spaces = width - (names[0].Length + names[1].Length); 57 | string_lists[list_idx].Add(new colorstring(names[0],ided_color[0],"".PadRight(num_spaces),Color.Black,names[1],ided_color[1])); 58 | item_idx += 2; 59 | } 60 | if(item_list.Count % 2 == 1){ 61 | ConsumableType ct = item_list.Last(); 62 | string n = (ct.ToString()[0] + ct.ToString().Substring(1).ToLower()).Replace('_',' '); 63 | //name = name[i].Replace('_',' '); 64 | Color ided_color = Color.DarkGray; 65 | if(IDed[ct]){ 66 | ided_color = Color.Cyan; 67 | } 68 | int num_spaces = width - n.Length; 69 | string_lists[list_idx].Add(new colorstring(n,ided_color,"".PadRight(num_spaces),Color.Black)); 70 | } 71 | ++list_idx; 72 | } 73 | Screen.WriteMapString(0,0,"".PadRight(COLS,'-')); 74 | for(int i=1;i messages = B.GetMessageLog(); 116 | MouseUI.PushButtonMap(MouseMode.ScrollableMenu); 117 | UI.draw_bottom_commands = false; 118 | UI.darken_status_bar = true; 119 | MouseUI.CreateMapButton(ConsoleKey.OemMinus,false,0,1); 120 | MouseUI.CreateMapButton(ConsoleKey.OemPlus,false,text_height + 1,1); 121 | Screen.WriteMapString(0,0,"".PadRight(COLS,'-')); 122 | Screen.WriteMapString(text_height + 1,0,"".PadRight(COLS,'-')); 123 | ConsoleKeyInfo command; 124 | char ch; 125 | int startline = Math.Max(0,messages.Count - text_height); 126 | for(bool done = false;!done;){ 127 | if(startline > 0){ 128 | Screen.WriteMapString(0,COLS-3,new colorstring("[",Color.Yellow,"-",Color.Cyan,"]",Color.Yellow)); 129 | } 130 | else{ 131 | Screen.WriteMapString(0,COLS-3,"---"); 132 | } 133 | bool more = false; 134 | if(startline + text_height < messages.Count){ 135 | more = true; 136 | } 137 | if(more){ 138 | Screen.WriteMapString(text_height + 1,COLS-3,new colorstring("[",Color.Yellow,"+",Color.Cyan,"]",Color.Yellow)); 139 | } 140 | else{ 141 | Screen.WriteMapString(text_height + 1,COLS-3,"---"); 142 | } 143 | for(int i=1;i<=text_height;++i){ 144 | if(messages.Count - startline < i){ 145 | Screen.WriteMapString(i,0,"".PadToMapSize()); 146 | } 147 | else{ 148 | Screen.WriteMapString(i,0,messages[i+startline-1].PadToMapSize()); 149 | } 150 | } 151 | UI.Display("Previous messages: "); 152 | command = Input.ReadKey(); 153 | ConsoleKey ck = command.Key; 154 | switch(ck){ 155 | case ConsoleKey.Backspace: 156 | case ConsoleKey.PageUp: 157 | case ConsoleKey.NumPad9: 158 | ch = (char)8; 159 | break; 160 | case ConsoleKey.Enter: 161 | ch = ' '; //hackery ahoy - enter becomes space and pagedown becomes enter. 162 | break; 163 | case ConsoleKey.PageDown: 164 | case ConsoleKey.NumPad3: 165 | ch = (char)13; 166 | break; 167 | case ConsoleKey.Home: 168 | case ConsoleKey.NumPad7: 169 | ch = '['; 170 | break; 171 | case ConsoleKey.End: 172 | case ConsoleKey.NumPad1: 173 | ch = ']'; 174 | break; 175 | default: 176 | ch = command.GetCommandChar(); 177 | break; 178 | } 179 | switch(ch){ 180 | case ' ': 181 | case (char)27: 182 | done = true; 183 | break; 184 | case '8': 185 | case '-': 186 | case '_': 187 | if(startline > 0){ 188 | --startline; 189 | } 190 | break; 191 | case '2': 192 | case '+': 193 | case '=': 194 | if(more){ 195 | ++startline; 196 | } 197 | break; 198 | case (char)8: 199 | if(startline > 0){ 200 | startline -= text_height; 201 | if(startline < 0){ 202 | startline = 0; 203 | } 204 | } 205 | break; 206 | case (char)13: 207 | if(messages.Count > text_height){ 208 | startline += text_height; 209 | if(startline + text_height > messages.Count){ 210 | startline = messages.Count - text_height; 211 | } 212 | } 213 | break; 214 | case '[': 215 | startline = 0; 216 | break; 217 | case ']': 218 | startline = Math.Max(0,messages.Count - text_height); 219 | break; 220 | default: 221 | break; 222 | } 223 | } 224 | if(show_footsteps && player.HasAttr(AttrType.DETECTING_MOVEMENT) && Actor.previous_footsteps.Count > 0){ 225 | M.Draw(); 226 | Screen.AnimateMapCells(Actor.previous_footsteps,new colorchar('!',Color.Red),150); 227 | } 228 | MouseUI.PopButtonMap(); 229 | UI.draw_bottom_commands = true; 230 | UI.darken_status_bar = false; 231 | } 232 | public static bool Telekinesis(bool cast,Actor user,Tile t){ 233 | bool wand = !cast; 234 | if(t == null){ 235 | return false; 236 | } 237 | if(t != null){ 238 | if(wand && user == player && !Item.identified[ConsumableType.TELEKINESIS]){ 239 | Item.identified[ConsumableType.TELEKINESIS] = true; 240 | B.Add("(It was a wand of telekinesis!) "); 241 | B.Print(true); 242 | } 243 | List ai_line = null; 244 | if(user != player && t == player.tile()){ 245 | 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)); 246 | if(fires.Count > 0){ 247 | ai_line = player.GetBestExtendedLineOfEffect(fires.Random()); 248 | } 249 | else{ 250 | if(wand){ 251 | ai_line = player.GetBestExtendedLineOfEffect(user); 252 | } 253 | else{ 254 | ai_line = player.GetBestExtendedLineOfEffect(player.TileInDirection(Global.RandomDirection())); 255 | } 256 | } 257 | } 258 | Actor a = t.actor(); 259 | if(a == null && t.inv == null && !t.Is(FeatureType.GRENADE) && t.Is(FeatureType.TROLL_CORPSE,FeatureType.TROLL_BLOODWITCH_CORPSE)){ 260 | ActorType troll_type = t.Is(FeatureType.TROLL_CORPSE)? ActorType.TROLL : ActorType.TROLL_BLOODWITCH; 261 | FeatureType troll_corpse = t.Is(FeatureType.TROLL_CORPSE)? FeatureType.TROLL_CORPSE : FeatureType.TROLL_BLOODWITCH_CORPSE; 262 | Event troll_event = Q.FindTargetedEvent(t,EventType.REGENERATING_FROM_DEATH); 263 | troll_event.dead = true; 264 | Actor troll = Actor.Create(troll_type,t.row,t.col); 265 | foreach(Event e in Q.list){ 266 | if(e.target == troll && e.type == EventType.MOVE){ 267 | e.tiebreaker = troll_event.tiebreaker; 268 | e.dead = true; 269 | break; 270 | } 271 | } 272 | Actor.tiebreakers[troll_event.tiebreaker] = troll; 273 | troll.symbol = '%'; 274 | troll.attrs[AttrType.CORPSE] = 1; 275 | troll.Name = new Name(troll.GetName(Possessive) + " corpse"); 276 | troll.curhp = troll_event.value; 277 | troll.attrs[AttrType.PERMANENT_DAMAGE] = troll_event.secondary_value; 278 | troll.attrs[AttrType.NO_ITEM]++; 279 | t.features.Remove(troll_corpse); 280 | a = troll; 281 | } 282 | if(a != null){ 283 | string msg = "Throw " + a.GetName(true,The) + " in which direction? "; 284 | if(a == player){ 285 | msg = "Throw yourself in which direction? "; 286 | } 287 | List line = null; 288 | if(user == player){ 289 | TargetInfo info = a.GetTarget(false,12,0,false,true,false,msg); 290 | if(info != null){ 291 | line = info.extended_line; 292 | } 293 | } 294 | else{ 295 | line = ai_line; 296 | } 297 | if(line != null){ 298 | if(line.Count == 1 && line[0].actor() == user){ 299 | if(wand){ 300 | return true; 301 | } 302 | return false; 303 | } 304 | if(cast){ 305 | B.Add(user.GetName(false,The,Verb("cast")) + " telekinesis. ",user); 306 | user.MakeNoise(6); //should match spellVolume, hack 307 | if(a.type == ActorType.ALASI_BATTLEMAGE && !a.HasSpell(SpellType.TELEKINESIS)){ 308 | a.curmp += Spell.Tier(SpellType.TELEKINESIS); 309 | if(a.curmp > a.maxmp){ 310 | a.curmp = a.maxmp; 311 | } 312 | a.GainSpell(SpellType.TELEKINESIS); 313 | B.Add("Runes on " + a.GetName(false,The,Possessive) + " armor align themselves with the spell. ",a); 314 | } 315 | } 316 | if(a == user && a == player){ 317 | B.Add("You throw yourself forward. "); 318 | } 319 | else{ 320 | if(line.Count == 1){ 321 | B.Add(user.GetName(true,The,Verb("throw")) + " " + a.GetName(true,The) + " into the ceiling. ",user,a); 322 | } 323 | else{ 324 | B.Add(user.GetName(true,The,Verb("throw")) + " " + a.GetName(true,The) + ". ",user,a); 325 | } 326 | } 327 | B.DisplayContents(); 328 | user.attrs[AttrType.SELF_TK_NO_DAMAGE] = 1; 329 | a.attrs[AttrType.TELEKINETICALLY_THROWN] = 1; 330 | a.attrs[AttrType.TURN_INTO_CORPSE]++; 331 | if(line.Count == 1){ 332 | a.TakeDamage(DamageType.NORMAL,DamageClass.PHYSICAL,R.Roll(3,6),user,"colliding with the ceiling"); 333 | a.CollideWith(a.tile()); 334 | } 335 | else{ 336 | a.tile().KnockObjectBack(a,line,12,user); 337 | } 338 | a.attrs[AttrType.TELEKINETICALLY_THROWN] = 0; 339 | user.attrs[AttrType.SELF_TK_NO_DAMAGE] = 0; 340 | if(a.curhp <= 0 && a.HasAttr(AttrType.REGENERATES_FROM_DEATH)){ 341 | a.attrs[AttrType.TURN_INTO_CORPSE] = 0; 342 | a.attrs[AttrType.CORPSE] = 0; 343 | a.attrs[AttrType.FROZEN] = 0; 344 | a.attrs[AttrType.INVULNERABLE] = 0; 345 | a.attrs[AttrType.SHIELDED] = 0; 346 | a.attrs[AttrType.BLOCKING] = 0; 347 | a.curhp = 1; //this is all pretty hacky - perhaps I should relocate the regenerating corpse through other means. 348 | a.TakeDamage(DamageType.NORMAL,DamageClass.NO_TYPE,500,null); 349 | } 350 | else{ 351 | a.CorpseCleanup(); 352 | } 353 | } 354 | else{ 355 | if(wand){ 356 | return true; 357 | } 358 | return false; 359 | } 360 | } 361 | else{ 362 | bool blast_fungus = false; 363 | if(t.type == TileType.BLAST_FUNGUS && !t.Is(FeatureType.GRENADE,FeatureType.WEB,FeatureType.FORASECT_EGG,FeatureType.BONES)){ 364 | blast_fungus = true; 365 | } 366 | if(t.inv != null || blast_fungus){ 367 | Item i = t.inv; 368 | string itemname = ""; 369 | if(blast_fungus){ 370 | itemname = "the blast fungus"; 371 | } 372 | else{ 373 | itemname = i.GetName(true,The, Extra); 374 | } 375 | string msg = "Throw " + itemname + " in which direction? "; 376 | List line = null; 377 | if(user == player){ 378 | TargetInfo info = t.GetTarget(false,12,0,false,true,false,msg); 379 | if(info != null){ 380 | line = info.extended_line; 381 | } 382 | } 383 | else{ 384 | line = ai_line; 385 | } 386 | if(line != null){ 387 | if(line.Count > 13){ 388 | line = line.ToCount(13); //for range 12 389 | } 390 | if(cast){ 391 | B.Add(user.GetName(false,The,Verb("cast")) + " telekinesis. ",user); 392 | user.MakeNoise(6); //should match spellVolume 393 | } 394 | if(blast_fungus){ 395 | B.Add("The blast fungus is pulled from the floor. ",t); 396 | B.Add("Its fuse ignites! ",t); 397 | t.Toggle(null); 398 | i = Item.Create(ConsumableType.BLAST_FUNGUS,t.row,t.col); 399 | if(i != null){ 400 | i.other_data = 3; 401 | i.revealed_by_light = true; 402 | Q.Add(new Event(i,100,EventType.BLAST_FUNGUS)); 403 | Screen.AnimateMapCell(t.row,t.col,new colorchar('3',Color.Red),100); 404 | } 405 | } 406 | if(line.Count == 1){ 407 | B.Add(user.GetName(true,The,Verb("throw")) + " " + itemname + " into the ceiling. ",user,t); 408 | } 409 | else{ 410 | B.Add(user.GetName(true,The,Verb("throw")) + " " + itemname + ". ",user,t); 411 | } 412 | B.DisplayContents(); 413 | if(i.quantity > 1){ 414 | i.quantity--; 415 | bool revealed = i.revealed_by_light; 416 | i = new Item(i,-1,-1); 417 | i.revealed_by_light = revealed; 418 | } 419 | else{ 420 | t.inv = null; 421 | Screen.WriteMapChar(t.row,t.col,M.VisibleColorChar(t.row,t.col)); 422 | } 423 | bool trigger_traps = false; 424 | Tile t2 = line.LastBeforeSolidTile(); 425 | Actor first = user.FirstActorInLine(line); 426 | if(t2 == line.LastOrDefault() && first == null){ 427 | trigger_traps = true; 428 | } 429 | if(first != null){ 430 | t2 = first.tile(); 431 | } 432 | line = line.ToFirstSolidTileOrActor(); 433 | //if(line.Count > 0){ 434 | // line.RemoveAt(line.Count - 1); 435 | //} 436 | if(line.Count > 0){ 437 | line.RemoveAt(line.Count - 1); 438 | } 439 | { 440 | Tile first_unseen = null; 441 | foreach(Tile tile2 in line){ 442 | if(!tile2.seen){ 443 | first_unseen = tile2; 444 | break; 445 | } 446 | } 447 | if(first_unseen != null){ 448 | line = line.To(first_unseen); 449 | if(line.Count > 0){ 450 | line.RemoveAt(line.Count - 1); 451 | } 452 | } 453 | } 454 | if(line.Count > 0){ //or > 1 ? 455 | user.AnimateProjectile(line,i.symbol,i.color); 456 | } 457 | if(first == user){ 458 | B.Add(user.GetName(false,The,Verb("catch")) + " it! ",user); 459 | if(user.inv.Count < Global.MAX_INVENTORY_SIZE){ 460 | user.GetItem(i); 461 | } 462 | else{ 463 | B.Add("Your pack is too full to fit anything else. "); 464 | i.ignored = true; 465 | user.tile().GetItem(i); 466 | } 467 | } 468 | else{ 469 | if(first != null){ 470 | B.Add("It hits " + first.GetName(The) + ". ",first); 471 | } 472 | if(i.IsBreakable()){ 473 | B.Add($"{i.GetName(true,The,Extra,Verb("break"))}! ",t2); 474 | if(i.ItemClass == ConsumableClass.ORB){ 475 | i.Use(null,new List{t2}); 476 | } 477 | else{ 478 | i.CheckForMimic(); 479 | } 480 | t2.MakeNoise(4); 481 | } 482 | else{ 483 | t2.GetItem(i); 484 | } 485 | t2.MakeNoise(2); 486 | } 487 | if(first != null && first != user && first != player){ 488 | first.player_visibility_duration = -1; 489 | first.attrs[AttrType.PLAYER_NOTICED]++; 490 | } 491 | else{ 492 | if(trigger_traps && t2.IsTrap()){ 493 | t2.TriggerTrap(); 494 | } 495 | } 496 | } 497 | else{ 498 | if(wand){ 499 | return true; 500 | } 501 | return false; 502 | } 503 | } 504 | else{ 505 | 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))){ 506 | if(cast){ 507 | B.Add(user.GetName(false,The,Verb("cast")) + " telekinesis. ",user); 508 | user.MakeNoise(6); //should match spellVolume 509 | } 510 | if(t.Is(TileType.DOOR_C)){ 511 | B.Add("The door slams open. ",t); 512 | t.Toggle(null); 513 | } 514 | else{ 515 | if(t.Is(TileType.DOOR_O)){ 516 | B.Add("The door slams open. ",t); 517 | t.Toggle(null); 518 | } 519 | } 520 | if(t.Is(TileType.POISON_BULB)){ 521 | t.Bump(0); 522 | } 523 | if(t.Is(FeatureType.WEB)){ 524 | B.Add("The web is destroyed. ",t); 525 | t.RemoveFeature(FeatureType.WEB); 526 | } 527 | if(t.Is(FeatureType.FORASECT_EGG)){ 528 | B.Add("The egg is destroyed. ",t); 529 | t.RemoveFeature(FeatureType.FORASECT_EGG); //todo: forasect pathing? 530 | } 531 | if(t.Is(FeatureType.BONES)){ 532 | B.Add("The bones are scattered. ",t); 533 | t.RemoveFeature(FeatureType.BONES); 534 | } 535 | } 536 | else{ 537 | bool grenade = t.Is(FeatureType.GRENADE); 538 | bool barrel = t.Is(TileType.BARREL); 539 | bool flaming_barrel = barrel && t.IsBurning(); 540 | bool torch = t.Is(TileType.STANDING_TORCH); 541 | string feature_name = ""; 542 | int impact_damage_dice = 3; 543 | colorchar vis = new colorchar(t.symbol,t.color); 544 | switch(t.type){ 545 | case TileType.CRACKED_WALL: 546 | feature_name = "cracked wall"; 547 | break; 548 | case TileType.RUBBLE: 549 | feature_name = "pile of rubble"; 550 | break; 551 | case TileType.STATUE: 552 | feature_name = "statue"; 553 | break; 554 | } 555 | if(grenade){ 556 | impact_damage_dice = 0; 557 | feature_name = "grenade"; 558 | vis.c = ','; 559 | vis.color = Color.Red; 560 | } 561 | if(flaming_barrel){ 562 | feature_name = "flaming barrel of oil"; 563 | } 564 | if(barrel){ 565 | feature_name = "barrel"; 566 | } 567 | if(torch){ 568 | feature_name = "torch"; 569 | } 570 | if(feature_name == ""){ 571 | if(wand){ 572 | if(user == player){ 573 | B.Add("The wand grabs at empty space. ",t); 574 | } 575 | return true; 576 | } 577 | return false; 578 | } 579 | string msg = "Throw the " + feature_name + " in which direction? "; 580 | bool passable_hack = !t.passable; 581 | if(passable_hack){ 582 | t.passable = true; 583 | } 584 | List line = null; 585 | if(user == player){ 586 | TargetInfo info = t.GetTarget(false,12,0,false,true,false,msg); 587 | if(info != null){ 588 | line = info.extended_line; 589 | } 590 | } 591 | else{ 592 | line = ai_line; 593 | } 594 | if(passable_hack){ 595 | t.passable = false; 596 | } 597 | if(line != null){ 598 | if(cast){ 599 | B.Add(user.GetName(false,The,Verb("cast")) + " telekinesis. ",user); 600 | user.MakeNoise(6); //should match spellVolume 601 | } 602 | if(line.Count == 1){ 603 | B.Add(user.GetName(true,The,Verb("throw")) + " the " + feature_name + " into the ceiling. ",user,t); 604 | } 605 | else{ 606 | B.Add(user.GetName(true,The,Verb("throw")) + " the " + feature_name + ". ",user,t); 607 | } 608 | B.DisplayContents(); 609 | user.attrs[AttrType.SELF_TK_NO_DAMAGE] = 1; 610 | switch(t.type){ 611 | case TileType.CRACKED_WALL: 612 | case TileType.RUBBLE: 613 | case TileType.STATUE: 614 | case TileType.BARREL: 615 | case TileType.STANDING_TORCH: 616 | if(flaming_barrel){ 617 | t.RemoveFeature(FeatureType.FIRE); 618 | } 619 | t.Toggle(null,TileType.FLOOR); 620 | foreach(Tile neighbor in t.TilesAtDistance(1)){ 621 | neighbor.solid_rock = false; 622 | } 623 | break; 624 | } 625 | if(grenade){ 626 | t.RemoveFeature(FeatureType.GRENADE); 627 | Event e = Q.FindTargetedEvent(t,EventType.GRENADE); 628 | if(e != null){ 629 | e.dead = true; 630 | } 631 | } 632 | Screen.WriteMapChar(t.row,t.col,M.VisibleColorChar(t.row,t.col)); 633 | colorchar[,] mem = Screen.GetCurrentMap(); 634 | int current_row = t.row; 635 | int current_col = t.col; 636 | // 637 | int knockback_strength = 12; 638 | if(line.Count == 1){ 639 | knockback_strength = 0; 640 | } 641 | int i=0; 642 | while(true){ 643 | Tile t2 = line[i]; 644 | if(t2 == t){ 645 | break; 646 | } 647 | ++i; 648 | } 649 | line.RemoveRange(0,i+1); 650 | while(knockback_strength > 0){ //if the knockback strength is greater than 1, you're passing *over* at least one tile. 651 | Tile t2 = line[0]; 652 | line.RemoveAt(0); 653 | if(!t2.passable){ 654 | if(t2.Is(TileType.CRACKED_WALL,TileType.DOOR_C,TileType.HIDDEN_DOOR) && impact_damage_dice > 0){ 655 | string tilename = t2.GetName(true,The); 656 | if(t2.type == TileType.HIDDEN_DOOR){ 657 | tilename = "a hidden door"; 658 | t2.Toggle(null); 659 | } 660 | B.Add("The " + feature_name + " flies into " + tilename + ". ",t2); 661 | t2.Toggle(null); 662 | Screen.WriteMapChar(current_row,current_col,mem[current_row,current_col]); 663 | } 664 | else{ 665 | B.Add("The " + feature_name + " flies into " + t2.GetName(true,The) + ". ",t2); 666 | if(impact_damage_dice > 0){ 667 | t2.Bump(M.tile[current_row,current_col].DirectionOf(t2)); 668 | } 669 | Screen.WriteMapChar(current_row,current_col,mem[current_row,current_col]); 670 | } 671 | knockback_strength = 0; 672 | break; 673 | } 674 | else{ 675 | if(t2.actor() != null){ 676 | B.Add("The " + feature_name + " flies into " + t2.actor().GetName(true,The) + ". ",t2); 677 | if(t2.actor().type != ActorType.SPORE_POD && !t2.actor().HasAttr(AttrType.SELF_TK_NO_DAMAGE)){ 678 | t2.actor().TakeDamage(DamageType.NORMAL,DamageClass.PHYSICAL,R.Roll(impact_damage_dice,6),user,"colliding with a " + feature_name); 679 | } 680 | knockback_strength = 0; 681 | Screen.WriteMapChar(t2.row,t2.col,vis); 682 | Screen.WriteMapChar(current_row,current_col,mem[current_row,current_col]); 683 | current_row = t2.row; 684 | current_col = t2.col; 685 | break; 686 | } 687 | else{ 688 | if(t2.Is(FeatureType.WEB)){ //unless perhaps grenades should get stuck and explode in the web? 689 | t2.RemoveFeature(FeatureType.WEB); 690 | } 691 | Screen.WriteMapChar(t2.row,t2.col,vis); 692 | Screen.WriteMapChar(current_row,current_col,mem[current_row,current_col]); 693 | current_row = t2.row; 694 | current_col = t2.col; 695 | Screen.GLUpdate(); 696 | Thread.Sleep(20); 697 | } 698 | } 699 | //M.Draw(); 700 | knockback_strength--; 701 | } 702 | Tile current = M.tile[current_row,current_col]; 703 | if(grenade){ 704 | B.Add("The grenade explodes! ",current); 705 | current.ApplyExplosion(1,user,"an exploding grenade"); 706 | } 707 | if(barrel){ 708 | B.Add("The barrel smashes! ",current); 709 | List cone = current.TilesWithinDistance(1).Where(x=>x.passable); 710 | List added = new List(); 711 | foreach(Tile t3 in cone){ 712 | foreach(int dir in U.FourDirections){ 713 | if(R.CoinFlip() && t3.TileInDirection(dir).passable){ 714 | added.AddUnique(t3.TileInDirection(dir)); 715 | } 716 | } 717 | } 718 | cone.AddRange(added); 719 | foreach(Tile t3 in cone){ 720 | t3.AddFeature(FeatureType.OIL); 721 | if(t3.actor() != null && !t3.actor().HasAttr(AttrType.OIL_COVERED,AttrType.SLIMED)){ 722 | if(t3.actor().IsBurning()){ 723 | t3.actor().ApplyBurning(); 724 | } 725 | else{ 726 | t3.actor().attrs[AttrType.OIL_COVERED] = 1; 727 | B.Add(t3.actor().GetName(false,The,Are) + " covered in oil. ",t3.actor()); 728 | if(t3.actor() == player){ 729 | Help.TutorialTip(TutorialTopic.Oiled); 730 | } 731 | } 732 | } 733 | } 734 | if(flaming_barrel){ 735 | current.ApplyEffect(DamageType.FIRE); 736 | } 737 | } 738 | if(torch){ 739 | current.AddFeature(FeatureType.FIRE); 740 | } 741 | user.attrs[AttrType.SELF_TK_NO_DAMAGE] = 0; 742 | } 743 | else{ 744 | if(wand){ 745 | return true; 746 | } 747 | return false; 748 | } 749 | } 750 | } 751 | } 752 | } 753 | else{ 754 | return false; 755 | } 756 | return true; 757 | } 758 | public static void DebugDisplayPositions(IEnumerable list){ 759 | var temp = Screen.GetCurrentScreen(); 760 | foreach(pos p in list){ 761 | Screen.WriteMapChar(p.row,p.col,'!',Color.Red); 762 | } 763 | Input.ReadKey(); 764 | Screen.WriteArray(0,0,temp); 765 | } 766 | public static void DebugDisplayDijkstra(PosArray d){ DebugDisplayDijkstra(d,1); } 767 | public static void DebugDisplayDijkstra(PosArray d,int default_cost){ 768 | int h = d.objs.GetLength(0); 769 | int w = d.objs.GetLength(1); 770 | for(int i=0;i 2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {392222EF-9EEE-45F8-AFAE-260D9D06C4C9} 8 | WinExe 9 | Properties 10 | Forays 11 | Forays 12 | 512 13 | 14 | publish\ 15 | true 16 | Disk 17 | false 18 | Foreground 19 | 7 20 | Days 21 | false 22 | false 23 | true 24 | 0 25 | 1.0.0.%2a 26 | false 27 | false 28 | true 29 | False 30 | ForaysImages\forays.ico 31 | 32 | 33 | true 34 | full 35 | false 36 | bin\Debug\ 37 | DEBUG;TRACE 38 | prompt 39 | 4 40 | false 41 | 42 | 43 | AnyCPU 44 | pdbonly 45 | true 46 | bin\Release\ 47 | TRACE 48 | prompt 49 | 4 50 | 51 | 52 | Forays.Game 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | ..\packages\OpenTK.Next.1.1.1616.8959\lib\net20\OpenTK.dll 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | Designer 95 | 96 | 97 | 98 | 99 | 100 | Always 101 | 102 | 103 | Always 104 | 105 | 106 | Always 107 | 108 | 109 | Always 110 | 111 | 112 | Always 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | Always 129 | 130 | 131 | PreserveNewest 132 | 133 | 134 | 135 | 136 | False 137 | .NET Framework 3.5 SP1 Client Profile 138 | false 139 | 140 | 141 | False 142 | .NET Framework 3.5 SP1 143 | true 144 | 145 | 146 | 147 | 148 | 155 | -------------------------------------------------------------------------------- /Forays/ForaysHelp/advanced_help.txt: -------------------------------------------------------------------------------- 1 | Tips and advanced topics: 2 | 3 | Damage: 4 | 5 | Damage values are described as die rolls. For instance, a 2d6 6 | attack will deal damage equal to two dice with six sides each, 7 | added together. 8 | 9 | 10 | 11 | Stealth: 12 | 13 | Several factors determine whether you'll be spotted on a given 14 | turn: Your stealth skill, your distance from enemies, whether 15 | you're in darkness or light, and how long you've been within 16 | the sight range of enemies. 17 | 18 | Here's the formula: 19 | 20 | Skill * Distance * 10 - TurnsVisible * 5 21 | 22 | The result is your chance to remain unnoticed, in percentage. 23 | (100 and above will always succeed.) 24 | Darkness gives a +2 bonus to stealth skill in this calculation. 25 | Animals and some other foes are harder to sneak past - they use 26 | a 5 instead of a 10 in the formula. 27 | 28 | Because of this, you're much more likely to remain unseen 29 | if you stay two or more spaces away. Enemies automatically see 30 | you if you're carrying a light source, or if you're wearing 31 | plate mail and simply standing in the light. Making a noise 32 | while in plain sight will also get you spotted. 33 | 34 | 35 | 36 | Noise volumes: 37 | 38 | The volume of a noise is how far away it can be heard. The 39 | sound doesn't pass through solid tiles, but does travel around 40 | corners - basically, wherever you could walk to in that many 41 | steps. 42 | 43 | Explosions, alarm traps, and scrolls of thunderclap: 12 44 | Combat: 8 (from both parties) 45 | Collapse spell: 8 (from target) 46 | Words of magic (spells & scrolls): 6 47 | All monster noises (growls, yells, etc.): 6 48 | Items shattering (potions & orbs): 4 49 | Walking on gravel: 3 50 | Any action (except standing still) in full plate: 3 51 | Changing armor: 3 52 | Items landing after being thrown: 2 53 | Switching weapons: 1 54 | Trap activation: 1 55 | Using an item: 1 56 | 57 | 58 | 59 | 60 | With special thanks to the people of #forays, #rgrd and -ot. 61 | -------------------------------------------------------------------------------- /Forays/ForaysHelp/feat_help.txt: -------------------------------------------------------------------------------- 1 | Skills: 2 | 3 | Combat: Measures your deadliness with blade and bow. 4 | Each level grants +1 damage and +1% to hit with all 5 | weapons. 6 | 7 | 8 | Defense: Measures your ability to avoid physical harm. 9 | Each level adds 3% to your chance to avoid attacks. 10 | 11 | 12 | Magic: Measures your ability to cast spells. 13 | Each level grants another spell and increases your 14 | mana by 5. 15 | 16 | 17 | Spirit: Measures your willpower and ability to tap into your 18 | emotions, whether devotion or rage. 19 | Each level increases your chance to ignore certain 20 | status effects by 8%. 21 | 22 | 23 | Stealth: Measures your ability to avoid notice. 24 | Each level increases your chance to remain unseen by 25 | enemies. 26 | 27 | 28 | Combat feats: 29 | 30 | Quick draw - Switch from a melee weapon to another weapon 31 | instantly. (You can fire arrows without first 32 | switching to your bow. However, putting your bow 33 | away still takes time.) 34 | 35 | Whirlwind style - When you take a step, you automatically 36 | attack every enemy still adjacent to you. 37 | 38 | Lunge - Leap from one space away and attack your target with 39 | perfect accuracy. The intervening space must be 40 | unoccupied. 41 | 42 | Drive back - Enemies must yield ground in order to avoid your 43 | attacks, and cornered enemies are more vulnerable 44 | to critical hits. (When your target has nowhere 45 | to run, your attacks won't miss.) 46 | 47 | 48 | 49 | Defense feats: 50 | 51 | Armor mastery - When your armor blocks an enemy's attack, 52 | you're twice as likely to score a critical hit 53 | on it next turn. The exhaustion total at which 54 | armor becomes too heavy is increased by 25%. 55 | 56 | Cunning dodge - Relying on mobility and tricks, you can 57 | entirely avoid the first attack of each foe 58 | you face. 59 | 60 | Deflect attack - When an enemy attacks you, you might deflect 61 | the attack to any other enemy next to you. 62 | (The two enemies don't need to be adjacent to 63 | one another.) 64 | 65 | Tumble - Move up to two spaces while avoiding arrows. Takes two 66 | turns to perform. 67 | 68 | 69 | 70 | Magic feats: 71 | 72 | Master's edge - The first offensive spell you've learned is 73 | empowered - it'll deal 1d6 extra damage. 74 | (Affects the first spell in the list that 75 | deals damage directly.) 76 | 77 | Arcane interference - When you cast a spell, nearby enemy 78 | spellcasters are stunned for a short 79 | time. Additionally, they permanently 80 | lose their ability to cast the spell 81 | you just cast. 82 | 83 | Chain casting - The mana cost of your spells is reduced by one 84 | if you cast a spell last turn. (This doesn't 85 | ever reduce mana costs to zero.) 86 | 87 | Force of will - Exhaustion will never make you fail an attempt 88 | at casting. (Casting without mana still 89 | increases your exhaustion, and you can't cast 90 | if your exhaustion would exceed 100%.) 91 | 92 | 93 | 94 | Spirit feats: 95 | 96 | Conviction - Each turn you're engaged in combat (attacking or 97 | being attacked) you gain 1 bonus Spirit, and 98 | bonus Combat skill equal to half that, rounded up. 99 | 100 | Enduring soul - Your health recovers over time. You'll regain 101 | one HP every five turns until your total health 102 | is a multiple of 10. 103 | 104 | Feel no pain - When your health becomes very low (less than 105 | 20%), you briefly enter a state of 106 | invulnerability. (For about 5 turns, you'll be 107 | immune to damage, but not other effects.) 108 | 109 | Boiling blood - Taking damage briefly increases your movement 110 | speed. (This effect can stack up to 5 times. 111 | At 5 stacks, your speed is doubled and you are 112 | immune to fire damage.) 113 | 114 | 115 | 116 | Stealth feats: 117 | 118 | Neck snap - Automatically perform a stealth kill when attacking 119 | an unaware medium humanoid. (Living enemies of 120 | approximately human size.) 121 | 122 | Disarm trap - Alter a trap so that you can walk past it safely 123 | (but enemies still trigger it). You can use this 124 | feat again to disable it entirely. 125 | 126 | Corner climb - If you're in a corner (and in the dark), enemies 127 | can't see you unless they're adjacent to you. 128 | 129 | Danger sense - Once you've seen or detected an enemy, you can 130 | sense where it's safe and where that enemy might 131 | detect you. Your torch must be extinguished 132 | while you're sneaking. 133 | 134 | 135 | -------------------------------------------------------------------------------- /Forays/ForaysHelp/help.txt: -------------------------------------------------------------------------------- 1 | (Forays into Norrendrin is a roguelike game. Don't know what a 2 | roguelike is? Visit www.roguebasin.com for more info.) 3 | 4 | 5 | How to play: 6 | 7 | (Forays has in-game help in the form of tutorial tips that pop 8 | up to explain each new concept the first time you encounter it, 9 | so feel free to exit Help and jump right in!) 10 | 11 | 12 | Important commands to remember are [e] to examine and change 13 | your equipment, and [a] to use your consumable items. 14 | If your health drops, press [r] to rest - but only once per 15 | level! 16 | 17 | 18 | Your torch illuminates your surroundings, but also gives away 19 | your position. If you wish to avoid notice, put away your 20 | torch with [t]. ([t] will also bring it back out again.) 21 | 22 | 23 | Press Tab to look at your surroundings. (Pressing Escape or 24 | Space will exit most menus and selections.) 25 | 26 | 27 | If you've learned a spell, you can cast it by pressing [z]. 28 | 29 | 30 | You can view and activate feats from the character screen [c]. 31 | (Some feats are activated, while some are entirely passive.) 32 | 33 | 34 | Try switching to your bow and firing some arrows([e] to switch, 35 | [s] to shoot). You always have access to five different weapons 36 | and three different armors. 37 | 38 | 39 | Remember that you don't need to kill every enemy you encounter. 40 | Some enemies - mostly humanoids - can carry consumable items, 41 | though. 42 | 43 | 44 | When you find stairs, you can walk down them with [>]. (It's a 45 | one-way trip, so don't leave anything behind!) 46 | 47 | 48 | The full command list can be accessed in-game by pressing [-]. 49 | 50 | 51 | Command list: 52 | 53 | Tab : Look around (Tab again to cycle between targets) 54 | r : Rest to recover health/mana/exhaustion & repair equipment 55 | t : Turn your torch on/off z : Cast a spell 56 | e : View and change equipment s : Fire an arrow 57 | 58 | i : View your inventory 59 | a : Use a consumable item f : Fling an item 60 | g : Pick up an item d : Drop an item 61 | 62 | x : Explore automatically until something happens 63 | X : Travel to a specific location . : Wait one turn 64 | > : Take stairs to next level o : Operate terrain 65 | w : Walk automatically in a direction m : View dungeon map 66 | 67 | p : View previous messages \ : View known items 68 | c : View character info, including feats/other active abilities 69 | 70 | q : Quit or save = : Change options 71 | ? : View the help screen - : View this command list 72 | Alt+Enter : Fullscreen 73 | 74 | (You can also switch weapons without using the equipment screen 75 | with 1-5 on the top row of the keyboard, & with 8-0 for armor.) 76 | -------------------------------------------------------------------------------- /Forays/ForaysHelp/item_help.txt: -------------------------------------------------------------------------------- 1 | Potions: 2 | 3 | Potion of healing - This invaluable elixir will instantly 4 | restore you to full health. 5 | 6 | Potion of regeneration - The potent healing magic in this 7 | potion will steadily grant you health for 100 turns. 8 | 9 | Potion of stoneform - This potion will change you temporarily 10 | to unliving stone, granting a slight resistance to all 11 | damage. You'll no longer be able to catch fire, 12 | and no toxin, gas, or potion will affect you. 13 | 14 | Potion of vampirism - Consuming this potion will grant many of 15 | the powers of a true vampire. You'll fly and drain life from 16 | living enemies, but light will leave you vulnerable. 17 | 18 | Potion of brutish strength - Drinking this potion grants the 19 | strength of a juggernaut. For a short time you'll be able to 20 | smash through various dungeon features. Additionally, your 21 | attacks will deal maximum damage and knock foes back 5 22 | spaces, and you'll keep moving afterward if possible. 23 | 24 | Potion of roots - Drinking this potion will cause thick roots 25 | to grow from you, holding you tightly to the ground and 26 | providing defense against attacks. 27 | 28 | Potion of haste - Drinking this potion will double your 29 | movement speed and greatly enhance your reflexes. You'll 30 | sometimes dodge an attack by leaping to a nearby space. 31 | You're more likely to dodge in an open area, but be careful 32 | around dangerous terrain. 33 | 34 | Potion of silence - This potion will cause you to radiate an 35 | aura of silence, preventing all sounds within 2 spaces. This 36 | can help you remain stealthy, but leaves you unable to speak 37 | words of magic. 38 | 39 | Potion of cloaking - This potion will cover you in shadows, 40 | causing you to fade to invisibility while in darkness. 41 | 42 | Potion of mystic mind - This potion will expand your mind, 43 | allowing you to sense foes no matter where they are, and 44 | granting immunity to stuns, sleep, rage, confusion, and fear. 45 | 46 | 47 | Scrolls: 48 | 49 | Scroll of blinking - This scroll will teleport you a short 50 | distance randomly. 51 | 52 | Scroll of passage - This scroll will move you to the other side 53 | of an adjacent wall (but not diagonally). 54 | 55 | Scroll of time - Reading this scroll will halt the flow of time 56 | for a single turn, allowing you to take an extra action. 57 | 58 | Scroll of knowledge - Reading this scroll will grant knowledge 59 | of the items in your pack, and of the current level, 60 | including secret doors and traps. 61 | 62 | Scroll of sunlight - This scroll fills the level with sunlight, 63 | illuminating every corner of the dungeon. 64 | 65 | Scroll of darkness - This scroll covers the dungeon in a 66 | blanket of darkness that suppresses all light. 67 | 68 | Scroll of renewal - This scroll's magic will strip negative 69 | effects from your weapons & armor, charge any wands in your 70 | possession, and remove any slime or oil covering you. 71 | 72 | Scroll of calling - This scroll's magic will find the nearest 73 | foe and transport it next to you. Immobile creatures are 74 | immune to this effect. 75 | 76 | Scroll of trap clearing - This scroll will cause all traps 77 | within 12 spaces to be triggered simultaneously. 78 | 79 | Scroll of enchantment - This potent scroll will impart a 80 | permanent magical effect on the weapon you're holding. 81 | 82 | Scroll of thunderclap - When this scroll is read, a cacophonous 83 | crash of thunder sweeps outward, shattering fragile objects 84 | like potions & cracked walls, and damaging & stunning foes. 85 | 86 | Scroll of fire ring - This scroll will surround you with a ring 87 | of fire, just far enough to avoid its heat...if you stand 88 | still. 89 | 90 | Scroll of rage - When read, this scroll radiates a wave of fury 91 | over those nearby. Those affected will attack anything 92 | nearest to them, friend or foe. 93 | 94 | 95 | Orbs: 96 | 97 | Orb of freezing - Breaking this orb will encase nearby entities 98 | in ice. 99 | 100 | Orb of flames - Leaping flames will pour from this orb when it 101 | shatters. 102 | 103 | Orb of fog - Thick fog will expand from this orb when it 104 | breaks, blocking sight and reducing accuracy. 105 | 106 | Orb of detonation - On impact, this orb will explode violently, 107 | inflicting great damage on its surroundings. 108 | 109 | Orb of breaching - This orb will temporarily lower nearby 110 | walls, which will slowly return to their original state. 111 | 112 | Orb of shielding - This orb will create a zone of protection, 113 | shielding entities within for several turns. 114 | 115 | Orb of teleportal - Releasing this orb's energy will create an 116 | unstable rift. Creatures stepping into the rift will be 117 | transported elsewhere in the dungeon. 118 | 119 | Orb of pain - Anything caught in this orb's area of effect will 120 | become vulnerable to extra damage. 121 | 122 | Orb of blades - This orb's magic manifests in the form of 123 | several flying blades. These animated weapons fly quickly and 124 | attack anything nearby. 125 | 126 | Orb of confusion - Breaking this orb will release a befuddling 127 | gas, causing those affected to move and attack in random 128 | directions. 129 | 130 | 131 | Wands: 132 | 133 | Wand of dust storm - When zapped, this wand creates a billowing 134 | cloud of thick blinding dust, so heavy that it can extinguish 135 | flames. 136 | 137 | Wand of invisibility - This wand will render its target 138 | invisible. Carrying a light source (or being on fire) will 139 | still reveal an invisible being's location. 140 | 141 | Wand of flesh to fire - The target of this wand undergoes a 142 | grisly transfiguration as part of its substance turns to 143 | fire! It'll lose half of its current health and burst into 144 | flames. 145 | 146 | Wand of webs - This wand will create a line of sticky webs, 147 | stretching between you and the targeted space. 148 | 149 | Wand of slumber - The target of this wand will fall asleep (or 150 | become dormant, in the case of nonliving creatures) for a 151 | while. Sleepers will awaken upon taking damage as usual. 152 | 153 | Wand of reach - By zapping this wand, you can make a melee 154 | attack at range (with your current weapon) against your 155 | chosen target. 156 | 157 | Wand of telekinesis - After zapping this wand at your target, 158 | you can throw it forcefully. You can throw items, monsters, 159 | terrain features, and even yourself. 160 | 161 | 162 | -------------------------------------------------------------------------------- /Forays/ForaysHelp/spell_help.txt: -------------------------------------------------------------------------------- 1 | Spell tier 1: 2 | 3 | Radiance -- Increases your target's light radius by 2, then 4 | deals 1d6 damage to enemies within its new radius. 5 | 6 | Force palm -- Deals 1d6 magic damage to an adjacent target 7 | and knocks it backward 1 space. 8 | 9 | Detect movement -- Enemy movement will be indicated on the map. 10 | 11 | Flying leap -- Fly and move at double speed for a few turns. 12 | 13 | 14 | 15 | Spell tier 2: 16 | 17 | Mercurial sphere -- Deals 2d6 magic damage at range, and 18 | bounces 3 times (within 3 spaces). 19 | 20 | Grease -- Covers the target and adjacent tiles with oil. 21 | 22 | Blink -- Teleports you up to 8 spaces away (and at least 23 | 3 spaces away). 24 | 25 | Freeze -- Encases your target in ice until they can break out. 26 | 27 | 28 | 29 | Spell tier 3: 30 | 31 | Scorch -- Sets your target on fire. (Burning lasts for several 32 | turns and deals 2 or 3 damage each turn.) 33 | 34 | Lightning bolt -- A bolt leaps between foes (and some types of 35 | terrain), dealing 3d6 damage. 36 | 37 | Magic hammer -- Deals 4d6 magic damage to an adjacent target, 38 | stunning it for 2 turns. 39 | 40 | Portal -- Creates a portal. The first one won't be active until 41 | you create a second one. Each new portal will be 42 | linked with the previous ones. 43 | 44 | 45 | 46 | Spell tier 4: 47 | 48 | Passage -- Travel all the way through an adjacent wall. 49 | 50 | Amnesia -- Causes an adjacent enemy to forget your presence 51 | and do nothing for 4-5 turns. 52 | 53 | Stone spikes -- Radius 2. Stalagmites appear, dealing 4d6 54 | damage to enemies in their area. 55 | 56 | Shadowsight -- Grants low-light vision and increases 57 | perception in darkness. 58 | 59 | 60 | 61 | Spell tier 5: 62 | 63 | Blizzard -- A freezing storm deals 5d6 cold damage to each 64 | enemy within 5 spaces and slows them for 65 | several turns. 66 | 67 | Collapse -- Part of the dungeon crashes down on the chosen 68 | spot, dealing 3d6 damage to adjacent enemies. 69 | Adjacent walls will crack or break into rubble 70 | or gravel. A wall targeted directly will always 71 | be turned to gravel. 72 | 73 | Doom -- Deals 4d6 magic damage and leaves the target vulnerable 74 | for several turns. 75 | 76 | Telekinesis -- Target and throw enemies, items, many terrain 77 | features, or yourself. Most objects (except for 78 | items) will deal 3d6 damage on impact. 79 | 80 | 81 | 82 | Spell failure: 83 | 84 | The chance for a spell to fail depends upon your exhaustion and 85 | the spell's tier. The percentage is equal to: 86 | 87 | Exhaustion - 20 * (6 - Tier) 88 | 89 | Therefore: 90 | Every point of exhaustion above 20% adds to the failure rate of 91 | tier 5 spells. 92 | Every point of exhaustion above 40% adds to the failure rate of 93 | tier 4 spells. 94 | Every point of exhaustion above 60% adds to the failure rate of 95 | tier 3 spells. 96 | Every point of exhaustion above 80% adds to the failure rate of 97 | tier 2 spells. 98 | And tier 1 spells never fail, even at 100% exhaustion. 99 | 100 | -------------------------------------------------------------------------------- /Forays/ForaysImages/font10x20.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Forays/ForaysIntoNorrendrin/3ed15593a1e41aefd3694946490df3646c2278de/Forays/ForaysImages/font10x20.png -------------------------------------------------------------------------------- /Forays/ForaysImages/font12x18.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Forays/ForaysIntoNorrendrin/3ed15593a1e41aefd3694946490df3646c2278de/Forays/ForaysImages/font12x18.png -------------------------------------------------------------------------------- /Forays/ForaysImages/font12x24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Forays/ForaysIntoNorrendrin/3ed15593a1e41aefd3694946490df3646c2278de/Forays/ForaysImages/font12x24.png -------------------------------------------------------------------------------- /Forays/ForaysImages/font14x28.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Forays/ForaysIntoNorrendrin/3ed15593a1e41aefd3694946490df3646c2278de/Forays/ForaysImages/font14x28.png -------------------------------------------------------------------------------- /Forays/ForaysImages/font15x27.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Forays/ForaysIntoNorrendrin/3ed15593a1e41aefd3694946490df3646c2278de/Forays/ForaysImages/font15x27.png -------------------------------------------------------------------------------- /Forays/ForaysImages/font16x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Forays/ForaysIntoNorrendrin/3ed15593a1e41aefd3694946490df3646c2278de/Forays/ForaysImages/font16x32.png -------------------------------------------------------------------------------- /Forays/ForaysImages/font18x36.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Forays/ForaysIntoNorrendrin/3ed15593a1e41aefd3694946490df3646c2278de/Forays/ForaysImages/font18x36.png -------------------------------------------------------------------------------- /Forays/ForaysImages/font21x38.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Forays/ForaysIntoNorrendrin/3ed15593a1e41aefd3694946490df3646c2278de/Forays/ForaysImages/font21x38.png -------------------------------------------------------------------------------- /Forays/ForaysImages/font6x12.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Forays/ForaysIntoNorrendrin/3ed15593a1e41aefd3694946490df3646c2278de/Forays/ForaysImages/font6x12.png -------------------------------------------------------------------------------- /Forays/ForaysImages/font8x12.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Forays/ForaysIntoNorrendrin/3ed15593a1e41aefd3694946490df3646c2278de/Forays/ForaysImages/font8x12.png -------------------------------------------------------------------------------- /Forays/ForaysImages/font8x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Forays/ForaysIntoNorrendrin/3ed15593a1e41aefd3694946490df3646c2278de/Forays/ForaysImages/font8x16.png -------------------------------------------------------------------------------- /Forays/ForaysImages/forays.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Forays/ForaysIntoNorrendrin/3ed15593a1e41aefd3694946490df3646c2278de/Forays/ForaysImages/forays.ico -------------------------------------------------------------------------------- /Forays/ForaysImages/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Forays/ForaysIntoNorrendrin/3ed15593a1e41aefd3694946490df3646c2278de/Forays/ForaysImages/logo.png -------------------------------------------------------------------------------- /Forays/ForaysUtility.cs: -------------------------------------------------------------------------------- 1 | /*Copyright (c) 2016 Derrick Creamer 2 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation 3 | files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, 4 | distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 7 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 8 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.*/ 9 | using System; 10 | using System.Collections.Generic; 11 | 12 | namespace ForaysUtilities { 13 | 14 | /// 15 | /// A multi-line word-wrapping string buffer. 16 | /// 17 | public class StringWrapBuffer { 18 | public StringWrapBuffer(int maxLines,int maxLength) : this(maxLines,maxLength,new char[] {'-'},new char[] {' '}) { } 19 | 20 | /// If set to 0 or less, there's no limit to the number of lines. 21 | /// Must be 1 or greater. 22 | /// Separator characters that should be kept when they divide two words. 23 | /// For an example with '!' as a retained separator, "nine!three" becomes "nine!" and "three". 24 | /// Separator characters that should be discarded when they divide two words (during word wrap only). 25 | /// For an example with '!' as a discarded separator, "seven!four" becomes "seven" and "four". 26 | public StringWrapBuffer(int maxLines,int maxLength,IEnumerable retainedSeparators,IEnumerable discardedSeparators) { 27 | this.maxLines = maxLines; 28 | if(maxLength < 1) throw new ArgumentOutOfRangeException("maxLength",maxLength,"Max length must be at least 1."); 29 | this.maxLength = maxLength; 30 | this.contents = new List(); 31 | this.createNewLine = true; 32 | this.reservedWrapData = null; 33 | this.reservedSpace = 0; 34 | if(retainedSeparators == null) { 35 | this.retainedSeparators = new char[0]; 36 | } 37 | else { 38 | this.retainedSeparators = new HashSet(retainedSeparators); 39 | } 40 | if(discardedSeparators == null) { 41 | this.discardedSeparators = new char[0]; 42 | } 43 | else { 44 | this.discardedSeparators = new HashSet(discardedSeparators); 45 | } 46 | } 47 | public void Add(string s) { 48 | if(string.IsNullOrEmpty(s)) return; 49 | if(createNewLine) { 50 | s = RemoveLeadingDiscardedSeparators(s); 51 | if(s != "") { 52 | createNewLine = false; 53 | if(contents.Count == maxLines && reservedSpace > 0 && reservedWrapData != null) { 54 | var reserveSplit = SplitOverflow(reservedWrapData,maxLength - reservedSpace); 55 | reservedWrapData = null; 56 | contents[contents.Count - 1] = reserveSplit[0]; //this string is resplit to make room for reserved space 57 | reserveSplit[1] = RemoveLeadingDiscardedSeparators(reserveSplit[1]); 58 | contents.Add(reserveSplit[1] + s); //if there's overflow from *that* line, it gets added before our new addition. 59 | } 60 | else { 61 | reservedWrapData = null; 62 | contents.Add(s); 63 | } 64 | CheckForBufferOverflow(); 65 | CheckForLineOverflow(); 66 | } 67 | } 68 | else { 69 | contents[contents.Count - 1] += s; 70 | CheckForLineOverflow(); 71 | } 72 | } 73 | 74 | /// 75 | /// Empties the buffer, and returns the just-removed contents. 76 | /// 77 | public List Clear() { 78 | var previousContents = Contents; 79 | contents = new List(); 80 | createNewLine = true; 81 | reservedWrapData = null; 82 | return previousContents; 83 | } 84 | 85 | /// 86 | /// A list of all (non-empty) strings in the buffer. 87 | /// 88 | public List Contents => new List(contents); 89 | 90 | /// 91 | /// The maximum length of a single line in the buffer. 92 | /// (Note that changing this value will NOT affect any lines that have already wrapped; it will only affect the current line and future lines.) 93 | /// 94 | public int MaxLength { 95 | get { return maxLength; } 96 | set { 97 | if(value < 1) throw new ArgumentOutOfRangeException("value",value,"Max length must be at least 1."); 98 | if(value - reservedSpace <= 0) throw new ArgumentOutOfRangeException("value",value,"Max length must be greater than ReservedSpace."); 99 | if(maxLength != value) { 100 | maxLength = value; 101 | CheckForLineOverflow(); 102 | } 103 | } 104 | } 105 | 106 | /// 107 | /// The maximum number of lines in the buffer. If zero or a negative number, there is no limit. 108 | /// (Note: ReservedSpace is not respected during MaxLines changes.) 109 | /// 110 | public int MaxLines { 111 | get { return maxLines; } 112 | set { 113 | if(maxLines != value) { 114 | if(value > 0 && value < contents.Count) reservedWrapData = null; 115 | maxLines = value; 116 | CheckForBufferOverflow(); 117 | } 118 | } 119 | } 120 | 121 | /// 122 | /// Changes word wrap behavior on the final line of the buffer. 123 | /// When the final line wraps, the wrapping will reserve this many characters (at the end) before deciding where to split the string. 124 | /// (Note that this value changes HOW word wrap happens on the final line. It does not change WHEN word wrap happens -- it does not reduce the max length.) 125 | /// 126 | public int ReservedSpace { 127 | get { return reservedSpace; } 128 | set { 129 | if(value < 0) throw new ArgumentOutOfRangeException("value",value,"Reserved space cannot be negative."); 130 | if(maxLength - value <= 0) throw new ArgumentOutOfRangeException("value",value,"Reserved space must be less than MaxLength."); 131 | reservedSpace = value; 132 | } 133 | } 134 | 135 | /// 136 | /// If there are characters in the reserved space, this method will cause the current line to wrap 137 | /// in accordance with the reserved space. (This method will affect the current line even 138 | /// if it isn't the final line.) 139 | /// 140 | public void ConfirmReservedSpace() { 141 | if(contents.Count > 0 && contents[contents.Count - 1].Length > maxLength - reservedSpace) { 142 | string line = reservedWrapData ?? contents[contents.Count - 1]; 143 | var reservedSplit = SplitOverflow(line,maxLength - reservedSpace); 144 | reservedWrapData = null; 145 | contents[contents.Count - 1] = reservedSplit[0]; 146 | createNewLine = true; 147 | Add(reservedSplit[1]); 148 | } 149 | } 150 | 151 | /// 152 | /// Whenever the buffer overflows beyond its capacity, listeners to this event will receive the current contents of the buffer, not including any overflow. 153 | /// Afterward, those contents will be emptied, and the buffer will now contain only the remaining overflow. 154 | /// 155 | public event Action> BufferFull; 156 | 157 | protected int maxLines; 158 | protected int maxLength; 159 | protected ICollection retainedSeparators; 160 | protected ICollection discardedSeparators; 161 | protected List contents; 162 | protected bool createNewLine; 163 | protected string reservedWrapData; 164 | protected int reservedSpace; 165 | protected void CheckForLineOverflow() { 166 | while(!createNewLine && contents[contents.Count - 1].Length > maxLength) { 167 | createNewLine = true; //no matter what, THIS line is done -- no more will be added to it. 168 | var maxSplit = SplitOverflow(contents[contents.Count - 1],maxLength); 169 | maxSplit[1] = RemoveLeadingDiscardedSeparators(maxSplit[1]); 170 | if(maxSplit[1] != "") { //if the default overflow would be printed... 171 | if(reservedSpace > 0 && contents.Count == maxLines) { //if this is the last line (and if reserved space matters)... 172 | var reservedSplit = SplitOverflow(contents[contents.Count - 1],maxLength - reservedSpace); //calculate the reserved space split. 173 | contents[contents.Count - 1] = reservedSplit[0]; 174 | Add(reservedSplit[1]); 175 | } 176 | else { 177 | contents[contents.Count - 1] = maxSplit[0]; 178 | Add(maxSplit[1]); 179 | } 180 | } 181 | else { //if the default overflow won't be printed, make note of the original string for reserved-space purposes. 182 | reservedWrapData = contents[contents.Count - 1]; 183 | contents[contents.Count - 1] = maxSplit[0]; 184 | } 185 | } 186 | } 187 | protected string RemoveLeadingDiscardedSeparators(string s) { 188 | int idx = -1; 189 | for(int i = 0;i maxLines) { 203 | var fullBuffer = contents.GetRange(0,maxLines); 204 | contents = contents.GetRange(maxLines,contents.Count - maxLines); 205 | BufferFull?.Invoke(fullBuffer); 206 | } 207 | } 208 | protected string[] SplitOverflow(string s,int startIdx) { 209 | int overflowIdx = FindSplitIdx(s,startIdx); 210 | return new string[] { s.Substring(0,overflowIdx),s.Substring(overflowIdx) }; 211 | } 212 | protected int FindSplitIdx(string s, int startIdx) { 213 | if(startIdx >= s.Length) startIdx = s.Length - 1; 214 | for(int tentativeIdx = startIdx;true;--tentativeIdx) { //at each step, we check tentativeIdx and tentativeIdx-1. 215 | if(tentativeIdx <= 0) { //if 0 is reached, there are no separators in this string. 216 | return startIdx; 217 | } 218 | if(retainedSeparators.Contains(s[tentativeIdx - 1])) { //a retained separator on the left of the tentativeIdx is always a valid split. 219 | return tentativeIdx; 220 | } 221 | if(!discardedSeparators.Contains(s[tentativeIdx - 1]) && discardedSeparators.Contains(s[tentativeIdx])) { //don't stop at the first discarded separator. 222 | return tentativeIdx; 223 | } 224 | } 225 | } 226 | } 227 | } 228 | -------------------------------------------------------------------------------- /Forays/Global.cs: -------------------------------------------------------------------------------- 1 | /*Copyright (c) 2011-2016 Derrick Creamer 2 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation 3 | files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, 4 | distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 7 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 8 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.*/ 9 | using System; 10 | using System.IO; 11 | using System.Diagnostics; 12 | using System.Collections.Generic; 13 | using OpenTK.Graphics; 14 | using Utilities; 15 | using PosArrays; 16 | namespace Forays{ 17 | public static class Global{ 18 | public const string VERSION = "0.8.4"; 19 | public static bool LINUX = false; 20 | public static bool GRAPHICAL = false; 21 | public const int SCREEN_H = 28; 22 | public const int SCREEN_W = 88; 23 | public const int ROWS = 22; 24 | public const int COLS = 66; 25 | public const int MAP_OFFSET_ROWS = 3; 26 | public const int MAP_OFFSET_COLS = 21; 27 | public const int STATUS_WIDTH = 20; 28 | public const int MAX_LIGHT_RADIUS = 12; //the maximum POSSIBLE light radius. used in light calculations. 29 | public const int MAX_INVENTORY_SIZE = 20; 30 | public const int HIGH_SCORES = 25; 31 | public static bool GAME_OVER = false; 32 | public static bool BOSS_KILLED = false; 33 | public static bool QUITTING = false; 34 | public static bool SAVING = false; 35 | public static string KILLED_BY = ""; 36 | public const string ForaysImageResources = "Forays.ForaysImages."; 37 | public const int MESSAGE_LOG_LENGTH = 1000; 38 | 39 | public static Stopwatch Timer; 40 | 41 | public static Dictionary Options = new Dictionary(); 42 | public static bool Option(OptionType option){ 43 | bool result = false; 44 | Options.TryGetValue(option,out result); 45 | return result; 46 | } 47 | public static int RandomDirection(){ 48 | int result = R.Roll(8); 49 | if(result == 5){ 50 | result = 9; 51 | } 52 | return result; 53 | } 54 | public static string[][] title = new string[][]{new string[]{ 55 | "####### ", 56 | "####### ", 57 | "## # ", 58 | "## ", 59 | "## # ", 60 | "##### ", 61 | "##### ", 62 | "## # ### # ## ### # # ###", 63 | "## # # ## # # # # # ", 64 | "## # # # # # # # ## ", 65 | "## # # # # # # #", 66 | "## ### # ### ## # ### ", 67 | " # ", 68 | " # "}, 69 | new string[]{ 70 | "I N T O N O R R E N D R I N"} 71 | }; 72 | public static string RomanNumeral(int num){ 73 | string result = ""; 74 | while(num > 1000){ 75 | result = result + "M"; 76 | num -= 1000; 77 | } 78 | result = result + RomanPattern(num/100,'C','D','M'); 79 | num -= (num/100)*100; 80 | result = result + RomanPattern(num/10,'X','L','C'); 81 | num -= (num/10)*10; 82 | result = result + RomanPattern(num,'I','V','X'); 83 | return result; 84 | } 85 | private static string RomanPattern(int num,char one,char five,char ten){ 86 | switch(num){ 87 | case 1: 88 | return "" + one; 89 | case 2: 90 | return "" + one + one; 91 | case 3: 92 | return "" + one + one + one; 93 | case 4: 94 | return "" + one + five; 95 | case 5: 96 | return "" + five; 97 | case 6: 98 | return "" + five + one; 99 | case 7: 100 | return "" + five + one + one; 101 | case 8: 102 | return "" + five + one + one + one; 103 | case 9: 104 | return "" + one + ten; 105 | default: //0 106 | return ""; 107 | } 108 | } 109 | public static string GenerateCharacterName(){ 110 | List vowel = new List{"a","e","i","o","u","ei","a","e","i","o","u","a","e","i","o","u","a","e","i","o","a","e","o"}; 111 | List end_vowel = new List{"a","e","i","o","u","io","ia","a","e","i","o","a","e","i","o","a","e","o","a","e","o"}; 112 | List consonant = new List{"k","s","t","n","h","m","y","r","w","g","d","p","b","f","l","v","z","ch","br","cr","dr","fr","gr","kr","pr","tr","th","sc","sh","sk","sl","sm","sn","sp","st","s","t","n","m","r","g","d","p","b","l","k","s","t","n","m","d","p","b","l"}; 113 | List end_consonant = new List{"k","s","t","n","m","r","g","d","p","b","l","z","ch","th","sh","sk","sp","st","k","s","t","n","m","r","n","d","p","b","l","k","s","t","n","m","r","d","p","l","sk","th","st","d","m","s"}; 114 | string result = ""; 115 | if(R.OneIn(5)){ 116 | if(R.CoinFlip()){ 117 | result = vowel.Random() + consonant.Random() + vowel.Random() + consonant.Random() + vowel.Random() + end_consonant.Random(); 118 | } 119 | else{ 120 | result = vowel.Random() + consonant.Random() + vowel.Random() + consonant.Random() + end_vowel.Random(); 121 | } 122 | } 123 | else{ 124 | if(R.CoinFlip()){ 125 | result = consonant.Random() + vowel.Random() + consonant.Random() + vowel.Random() + consonant.Random() + vowel.Random() + end_consonant.Random(); 126 | } 127 | else{ 128 | result = consonant.Random() + vowel.Random() + consonant.Random() + vowel.Random() + consonant.Random() + end_vowel.Random(); 129 | } 130 | } 131 | result = result.Substring(0,1).ToUpper() + result.Substring(1); 132 | return result; 133 | } 134 | public static void CheckForVictory(bool circleDestroyed){ 135 | Map M = Actor.M; 136 | Actor player = Actor.player; // can't wait to change this setup. 137 | MessageBuffer B = Actor.B; 138 | if(M.CurrentLevelType != LevelType.Final) return; 139 | bool circles = false; 140 | bool demons = false; 141 | for(int i=0;i<5;++i){ 142 | Tile circle = M.tile[M.FinalLevelSummoningCircle(i)]; 143 | if(circle.TilesWithinDistance(3).Any(x=>x.type == TileType.DEMONIC_IDOL)){ 144 | circles = true; 145 | break; 146 | } 147 | } 148 | foreach(Actor a in M.AllActors()){ 149 | if(a.IsFinalLevelDemon() && a.curhp > 0){ 150 | demons = true; 151 | break; 152 | } 153 | } 154 | if(!circles){ 155 | if(!demons){ //victory 156 | player.curhp = 100; 157 | if(circleDestroyed){ 158 | B.Add(Priority.Important,"As the last summoning circle is destroyed, your victory gives you a new surge of strength. "); 159 | } 160 | else{ 161 | B.Add(Priority.Important,"As the last demon falls, your victory gives you a new surge of strength. "); 162 | } 163 | B.Add(Priority.Important,"Kersai's summoning has been stopped. His cult will no longer threaten the area. "); 164 | B.Add(Priority.Important,"You begin the journey home to deliver the news. "); 165 | B.Print(true); 166 | Global.GAME_OVER = true; 167 | Global.BOSS_KILLED = true; 168 | Global.KILLED_BY = "nothing"; 169 | } 170 | else{ 171 | if(circleDestroyed){ 172 | B.Add(Priority.Important,"The summoning circles have been destroyed, but demons yet remain! "); 173 | } 174 | } 175 | } 176 | } 177 | public static void LoadOptions(){ 178 | if(!File.Exists("options.txt")){ 179 | return; 180 | } 181 | using(StreamReader file = new StreamReader("options.txt")){ 182 | string s = ""; 183 | while(s.Length < 2 || s.Substring(0,2) != "--"){ 184 | s = file.ReadLine(); 185 | if(s.Length >= 2 && s.Substring(0,2) == "--"){ 186 | break; 187 | } 188 | string[] tokens = s.Split(' '); 189 | if(tokens[0].Length == 1){ 190 | char c = Char.ToUpper(tokens[0][0]); 191 | if(c == 'F' || c == 'T'){ 192 | OptionType option = (OptionType)(-1); 193 | bool valid = true; 194 | try{ 195 | option = (OptionType)Enum.Parse(typeof(OptionType),tokens[1],true); 196 | } 197 | catch(ArgumentException){ 198 | valid = false; 199 | } 200 | if(valid){ 201 | if(c == 'F'){ 202 | Options[option] = false; 203 | } 204 | else{ 205 | Options[option] = true; 206 | } 207 | } 208 | } 209 | } 210 | } 211 | s = ""; 212 | while(s.Length < 2 || s.Substring(0,2) != "--"){ 213 | s = file.ReadLine(); 214 | if(s.Length >= 2 && s.Substring(0,2) == "--"){ 215 | break; 216 | } 217 | string[] tokens = s.Split(' '); 218 | if(tokens[0].Length == 1){ 219 | char c = Char.ToUpper(tokens[0][0]); 220 | if(c == 'F' || c == 'T'){ 221 | TutorialTopic topic = (TutorialTopic)(-1); 222 | bool valid = true; 223 | try{ 224 | topic = (TutorialTopic)Enum.Parse(typeof(TutorialTopic),tokens[1],true); 225 | } 226 | catch(ArgumentException){ 227 | valid = false; 228 | } 229 | if(valid){ 230 | if(c == 'F' || Global.Option(OptionType.ALWAYS_RESET_TIPS)){ 231 | Help.displayed[topic] = false; 232 | } 233 | else{ 234 | Help.displayed[topic] = true; 235 | } 236 | } 237 | } 238 | } 239 | } 240 | } 241 | } 242 | public static void SaveOptions(){ 243 | using(StreamWriter file = new StreamWriter("options.txt",false)){ 244 | file.WriteLine("Options:"); 245 | file.WriteLine("Any line that starts with [TtFf] and a space MUST be one of the valid options(or, in the 2nd part, one of the valid tutorial tips):"); 246 | string optionNames = ""; 247 | foreach(OptionType op in Enum.GetValues(typeof(OptionType))){ 248 | if(op != OptionType.DISABLE_GRAPHICS){ 249 | optionNames += op.ToString().ToLower() + " "; 250 | } 251 | } 252 | file.WriteLine(optionNames); 253 | foreach(OptionType op in Enum.GetValues(typeof(OptionType))){ 254 | if(op != OptionType.DISABLE_GRAPHICS){ 255 | if(Option(op)){ 256 | file.Write("t "); 257 | } 258 | else{ 259 | file.Write("f "); 260 | } 261 | file.WriteLine(Enum.GetName(typeof(OptionType),op).ToLower()); 262 | } 263 | } 264 | file.WriteLine("-- Tracking which tutorial tips have been displayed:"); 265 | foreach(TutorialTopic topic in Enum.GetValues(typeof(TutorialTopic))){ 266 | if(Help.displayed[topic]){ 267 | file.Write("t "); 268 | } 269 | else{ 270 | file.Write("f "); 271 | } 272 | file.WriteLine(Enum.GetName(typeof(TutorialTopic),topic)); 273 | } 274 | file.WriteLine("--"); 275 | } 276 | } 277 | public delegate int IDMethod(PhysicalObject o); 278 | public static void SaveGame(MessageBuffer B,Map M,Queue Q){ //games are loaded in Main.cs 279 | FileStream file = new FileStream("forays.sav",FileMode.CreateNew); 280 | BinaryWriter b = new BinaryWriter(file); 281 | Dictionary id = new Dictionary(); 282 | int next_id = 1; 283 | IDMethod GetID = delegate(PhysicalObject o){ 284 | if(o == null){ 285 | return 0; 286 | } 287 | if(!id.ContainsKey(o)){ 288 | id.Add(o,next_id); 289 | ++next_id; 290 | } 291 | return id[o]; 292 | }; 293 | b.Write(Actor.player_name); 294 | b.Write(M.currentLevelIdx); 295 | b.Write(M.level_types.Count); 296 | foreach(LevelType lt in M.level_types){ 297 | b.Write((int)lt); 298 | } 299 | b.Write(M.wiz_lite); 300 | b.Write(M.wiz_dark); 301 | for(int i=0;i> groups = new List>(); 324 | b.Write(Actor.tiebreakers.Count); 325 | foreach(Actor a in Actor.tiebreakers){ 326 | if(a == null){ 327 | b.Write(GetID(a)); 328 | } 329 | else{ 330 | SaveActor(a,b,groups,GetID); 331 | } 332 | } 333 | b.Write(groups.Count); 334 | foreach(List group in groups){ 335 | b.Write(group.Count); 336 | foreach(Actor a in group){ 337 | b.Write(GetID(a)); 338 | } 339 | } 340 | b.Write(M.AllTiles().Count); 341 | foreach(Tile t in M.AllTiles()){ 342 | b.Write(GetID(t)); 343 | b.Write(t.row); 344 | b.Write(t.col); 345 | //todo name 346 | b.Write(t.symbol); 347 | b.Write((int)t.color); 348 | b.Write(t.light_radius); 349 | b.Write((int)t.type); 350 | b.Write(t.passable); 351 | b.Write(t.GetInternalOpacity()); 352 | b.Write(t.seen); 353 | b.Write(t.revealed_by_light); 354 | b.Write(t.solid_rock); 355 | b.Write(t.light_value); 356 | b.Write(t.direction_exited); 357 | if(t.toggles_into.HasValue){ 358 | b.Write(true); 359 | b.Write((int)t.toggles_into.Value); 360 | } 361 | else{ 362 | b.Write(false); 363 | } 364 | if(t.inv != null){ 365 | SaveItem(t.inv,b,GetID); 366 | } 367 | else{ 368 | b.Write(GetID(null)); 369 | } 370 | b.Write(t.features.Count); 371 | foreach(FeatureType f in t.features){ 372 | b.Write((int)f); 373 | } 374 | } 375 | b.Write(Q.turn); 376 | int num_events = 0; 377 | foreach(Event e in Q.list){ 378 | if(!e.dead){ 379 | ++num_events; 380 | } 381 | } 382 | b.Write(num_events); 383 | //b.Write(Q.list.Count); 384 | foreach(Event e in Q.list){ 385 | if(e.dead){ 386 | continue; 387 | } 388 | if(e.target is Item && !id.ContainsKey(e.target)){ //in this case, we have an item that isn't on the map or in an inventory, so we need to write all its info. 389 | b.Write(true); 390 | SaveItem(e.target as Item,b,GetID); 391 | } 392 | else{ 393 | b.Write(false); 394 | b.Write(GetID(e.target)); //in every other case, the target should already be accounted for. 395 | } 396 | if(e.area == null){ 397 | b.Write(0); 398 | } 399 | else{ 400 | b.Write(e.area.Count); 401 | foreach(Tile t in e.area){ 402 | b.Write(GetID(t)); 403 | } 404 | } 405 | b.Write(e.delay); 406 | b.Write((int)e.type); 407 | b.Write((int)e.attr); 408 | b.Write((int)e.feature); 409 | b.Write(e.value); 410 | b.Write(e.secondary_value); 411 | b.Write(e.msg); 412 | if(e.msg_objs == null){ 413 | b.Write(0); 414 | } 415 | else{ 416 | b.Write(e.msg_objs.Count); 417 | foreach(PhysicalObject o in e.msg_objs){ 418 | b.Write(GetID(o)); 419 | } 420 | } 421 | b.Write(e.time_created); 422 | b.Write(e.dead); 423 | b.Write(e.tiebreaker); 424 | } 425 | b.Write(Actor.footsteps.Count); 426 | foreach(pos p in Actor.footsteps){ 427 | b.Write(p.row); 428 | b.Write(p.col); 429 | } 430 | b.Write(Actor.previous_footsteps.Count); 431 | foreach(pos p in Actor.previous_footsteps){ 432 | b.Write(p.row); 433 | b.Write(p.col); 434 | } 435 | b.Write(Actor.interrupted_path.row); 436 | b.Write(Actor.interrupted_path.col); 437 | b.Write(UI.viewing_commands_idx); 438 | b.Write(M.feat_gained_this_level); 439 | b.Write(M.extra_danger); 440 | //b.Write(Item.unIDed_name.Count); 441 | //foreach(ConsumableType ct in Item.unIDed_name.Keys){ 442 | //b.Write((int)ct); 443 | //b.Write(Item.unIDed_name[ct]); todo broke saving here 444 | //} 445 | b.Write(Item.identified.hashSet.Count); 446 | foreach(ConsumableType ct in Item.identified){ 447 | b.Write((int)ct); 448 | b.Write(Item.identified[ct]); 449 | } 450 | b.Write(Item.proto.Keys.Count); 451 | foreach(ConsumableType ct in Item.proto.Keys){ 452 | b.Write((int)ct); 453 | b.Write((int)Item.proto[ct].color); 454 | } 455 | b.Write(Fire.burning_objects.Count); 456 | foreach(PhysicalObject o in Fire.burning_objects){ 457 | b.Write(GetID(o)); 458 | } 459 | for(int i=0;i> groups,IDMethod get_id){ 491 | b.Write(get_id(a)); 492 | b.Write(a.row); 493 | b.Write(a.col); 494 | //todo name 495 | b.Write(a.symbol); 496 | b.Write((int)a.color); 497 | b.Write((int)a.type); 498 | b.Write(a.maxhp); 499 | b.Write(a.curhp); 500 | b.Write(a.maxmp); 501 | b.Write(a.curmp); 502 | b.Write(a.speed); 503 | b.Write(a.light_radius); 504 | b.Write(get_id(a.target)); 505 | b.Write(a.inv.Count); 506 | foreach(Item i in a.inv){ 507 | SaveItem(i,b,get_id); 508 | } 509 | b.Write(a.attrs.d.Count); 510 | foreach(AttrType at in a.attrs.d.Keys){ 511 | b.Write((int)at); 512 | b.Write(a.attrs[at]); 513 | } 514 | b.Write(a.skills.d.Count); 515 | foreach(SkillType st in a.skills.d.Keys){ 516 | b.Write((int)st); 517 | b.Write(a.skills[st]); 518 | } 519 | b.Write(a.feats.d.Count); 520 | foreach(FeatType ft in a.feats.d.Keys){ 521 | b.Write((int)ft); 522 | b.Write(a.feats[ft]); 523 | } 524 | b.Write(a.spells.d.Count); 525 | foreach(SpellType sp in a.spells.d.Keys){ 526 | b.Write((int)sp); 527 | b.Write(a.spells[sp]); 528 | } 529 | b.Write(a.exhaustion); 530 | b.Write(a.time_of_last_action); 531 | b.Write(a.recover_time); 532 | b.Write(a.path.Count); 533 | foreach(pos p in a.path){ 534 | b.Write(p.row); 535 | b.Write(p.col); 536 | } 537 | b.Write(get_id(a.target_location)); 538 | b.Write(a.player_visibility_duration); 539 | if(a.group != null && groups != null){ 540 | groups.AddUnique(a.group); 541 | } 542 | b.Write(a.weapons.Count); 543 | foreach(Weapon w in a.weapons){ 544 | b.Write((int)w.type); 545 | b.Write((int)w.enchantment); 546 | b.Write(w.status.d.Count); 547 | foreach(EquipmentStatus st in w.status.d.Keys){ 548 | b.Write((int)st); 549 | b.Write(w.status[st]); 550 | } 551 | } 552 | b.Write(a.armors.Count); 553 | foreach(Armor ar in a.armors){ 554 | b.Write((int)ar.type); 555 | b.Write((int)ar.enchantment); 556 | b.Write(ar.status.d.Count); 557 | foreach(EquipmentStatus st in ar.status.d.Keys){ 558 | b.Write((int)st); 559 | b.Write(ar.status[st]); 560 | } 561 | } 562 | b.Write(a.magic_trinkets.Count); 563 | foreach(MagicTrinketType m in a.magic_trinkets){ 564 | b.Write((int)m); 565 | } 566 | } 567 | private static void SaveItem(Item i,BinaryWriter b,IDMethod get_id){ 568 | b.Write(get_id(i)); 569 | b.Write(i.row); 570 | b.Write(i.col); 571 | //todo name 572 | b.Write(i.symbol); 573 | b.Write((int)i.color); 574 | b.Write(i.light_radius); 575 | b.Write((int)i.type); 576 | b.Write(i.quantity); 577 | b.Write(i.charges); 578 | b.Write(i.other_data); 579 | b.Write(i.ignored); 580 | b.Write(i.do_not_stack); 581 | b.Write(i.revealed_by_light); 582 | } 583 | public static void Quit(){ 584 | if(LINUX && !Screen.GLMode){ 585 | Screen.Blank(); 586 | Screen.ResetColors(); 587 | Screen.SetCursorPosition(0,0); 588 | Screen.CursorVisible = true; 589 | } 590 | Environment.Exit(0); 591 | } 592 | } 593 | public static class Extensions{ 594 | public static List GetWordWrappedList(this string s,int max_length,bool match_length_exactly){ //max_length MUST be longer than any single word in the string 595 | List result = new List(); 596 | while(s.Length > max_length){ 597 | for(int i=max_length;i>=0;--i){ 598 | if(s[i] == ' '){ 599 | if(match_length_exactly){ 600 | result.Add(s.Substring(0,i).PadRight(max_length)); 601 | } 602 | else{ 603 | result.Add(s.Substring(0,i)); 604 | } 605 | s = s.Substring(i+1); 606 | break; 607 | } 608 | } 609 | } 610 | if(match_length_exactly){ 611 | result.Add(s.PadRight(max_length)); 612 | } 613 | else{ 614 | result.Add(s); 615 | } 616 | return result; 617 | } 618 | public static string ConcatenateListWithCommas(this List ls){ 619 | //"one" returns "one" 620 | //"one" "two" returns "one and two" 621 | //"one" "two" "three" returns "one, two, and three", and so on 622 | if(ls.Count == 1){ 623 | return ls[0]; 624 | } 625 | if(ls.Count == 2){ 626 | return ls[0] + " and " + ls[1]; 627 | } 628 | if(ls.Count > 2){ 629 | string result = ""; 630 | for(int i=0;i= s.Length) return -1; 654 | for(int n=wrap_index;n>=0;--n){ 655 | if(s[n] == ' '){ 656 | return n; 657 | } 658 | } 659 | return -1; 660 | } 661 | public static colorstring GetColorString(this string s){ return GetColorString(s,Color.Gray,Color.Cyan,Color.Black); } 662 | public static colorstring GetColorString(this string s,Color text_color){ return GetColorString(s,text_color,Color.Cyan,Color.Black); } 663 | public static colorstring GetColorString(this string s,Color text_color,Color key_color){ return GetColorString(s,text_color,key_color,Color.Black); } 664 | public static colorstring GetColorString(this string s,Color text_color,Color key_color,Color bg_color){ 665 | if(s.Contains("[")){ 666 | string temp = s; 667 | colorstring result = new colorstring(); 668 | while(temp.Contains("[")){ 669 | int open = temp.IndexOf('['); 670 | int close = temp.IndexOf(']'); 671 | if(close == -1){ 672 | result.strings.Add(new cstr(temp,text_color,bg_color)); 673 | temp = ""; 674 | } 675 | else{ 676 | int hyphen = temp.IndexOf('-'); 677 | if(hyphen != -1 && hyphen > open && hyphen < close){ 678 | result.strings.Add(new cstr(temp.Substring(0,open+1),text_color,bg_color)); 679 | //result.strings.Add(new cstr(temp.Substring(open+1,(close-open)-1),Color.Cyan)); 680 | result.strings.Add(new cstr(temp.Substring(open+1,(hyphen-open)-1),key_color,bg_color)); 681 | result.strings.Add(new cstr("-",text_color,bg_color)); 682 | result.strings.Add(new cstr(temp.Substring(hyphen+1,(close-hyphen)-1),key_color,bg_color)); 683 | result.strings.Add(new cstr("]",text_color,bg_color)); 684 | temp = temp.Substring(close+1); 685 | } 686 | else{ 687 | result.strings.Add(new cstr(temp.Substring(0,open+1),text_color,bg_color)); 688 | result.strings.Add(new cstr(temp.Substring(open+1,(close-open)-1),key_color,bg_color)); 689 | result.strings.Add(new cstr("]",text_color,bg_color)); 690 | temp = temp.Substring(close+1); 691 | } 692 | } 693 | } 694 | if(temp != ""){ 695 | result.strings.Add(new cstr(temp,text_color,bg_color)); 696 | } 697 | return result; 698 | } 699 | else{ 700 | return new colorstring(s,text_color,bg_color); 701 | } 702 | } 703 | public static List GetColorStrings(this List l){ 704 | List result = new List(); 705 | foreach(string s in l){ 706 | result.Add(s.GetColorString()); 707 | } 708 | return result; 709 | } 710 | public static void AddWithWrap(this List l,colorstring cs,int wrap_width,int indent = 0){ 711 | colorstring last = l.LastOrDefault(); 712 | colorstring remainder = null; 713 | if(last == null){ 714 | remainder = cs; 715 | } 716 | else{ 717 | if(last.Length() + cs.Length() <= wrap_width){ 718 | foreach(cstr s in cs.strings){ 719 | last.strings.Add(s); 720 | } 721 | } 722 | else{ 723 | int split_idx = cs.LastSpaceBeforeWrap(wrap_width - last.Length()); 724 | if(split_idx == -1){ 725 | remainder = cs; 726 | } 727 | else{ 728 | var split = cs.SplitAt(split_idx,true); 729 | foreach(cstr s in split[0].strings){ 730 | last.strings.Add(s); 731 | } 732 | remainder = split[1]; 733 | } 734 | } 735 | } 736 | while(remainder?.Length() > 0){ 737 | if(indent + remainder.Length() <= wrap_width){ 738 | l.Add(new colorstring("".PadRight(indent)) + remainder); 739 | break; 740 | } 741 | else{ 742 | colorstring next = new colorstring("".PadRight(indent)); 743 | l.Add(next); 744 | int split_idx = remainder.LastSpaceBeforeWrap(wrap_width - indent); 745 | bool remove_space = true; 746 | if(split_idx == -1){ // if there's nowhere better, just cut it off at the end of the line. 747 | split_idx = wrap_width - indent; 748 | remove_space = false; 749 | } 750 | var split = remainder.SplitAt(split_idx,remove_space); 751 | foreach(cstr s in split[0].strings){ 752 | next.strings.Add(s); 753 | } 754 | remainder = split[1]; 755 | } 756 | } 757 | } 758 | public static List ToFirstSolidTile(this List line){ 759 | List result = new List(); 760 | foreach(Tile t in line){ 761 | result.Add(t); 762 | if(!t.passable){ 763 | break; 764 | } 765 | } 766 | return result; 767 | } 768 | public static List ToFirstSolidTileOrActor(this List line){ 769 | List result = new List(); 770 | int idx = 0; 771 | foreach(Tile t in line){ 772 | result.Add(t); 773 | if(idx != 0){ //skip the first, as it is assumed to be the origin 774 | if(!t.passable || t.actor() != null){ 775 | break; 776 | } 777 | } 778 | ++idx; 779 | } 780 | return result; 781 | } 782 | public static List To(this List line,PhysicalObject o){ 783 | if(o == null){ 784 | return new List(line); 785 | } 786 | List result = new List(); 787 | foreach(Tile t in line){ 788 | result.Add(t); 789 | if(o.row == t.row && o.col == t.col){ 790 | break; 791 | } 792 | } 793 | return result; 794 | } 795 | public static List From(this List line,PhysicalObject o){ 796 | List result = new List(); 797 | bool found = false; 798 | foreach(Tile t in line){ 799 | if(o.row == t.row && o.col == t.col){ 800 | found = true; 801 | } 802 | if(found){ 803 | result.Add(t); 804 | } 805 | } 806 | return result; 807 | } 808 | public static List ToCount(this List line,int count){ 809 | if(line.Count <= count || count < 0){ 810 | return new List(line); 811 | } 812 | List result = new List(); 813 | for(int i=0;i FromCount(this List line,int count){ //note that ToCount(x) and FromCount(x) will both include the element at x. 819 | if(count <= 1){ 820 | return new List(line); 821 | } 822 | List result = new List(); 823 | int total = line.Count; 824 | for(int i=count-1;i line){ 830 | Tile result = null; 831 | foreach(Tile t in line){ 832 | if(!t.passable){ 833 | break; 834 | } 835 | else{ 836 | result = t; 837 | } 838 | } 839 | return result; 840 | } 841 | public static void StopAtBlockingTerrain(this List path){ 842 | int i = 0; 843 | foreach(pos p in path){ 844 | if(Actor.M.tile[p].BlocksConnectivityOfMap(false)){ 845 | break; 846 | } 847 | ++i; 848 | } 849 | if(i < path.Count){ 850 | path.RemoveRange(i,path.Count - i); 851 | } 852 | } 853 | public static void StopAtUnseenTerrain(this List path){ 854 | int i = 0; 855 | foreach(pos p in path){ 856 | if(!Actor.M.tile[p].seen){ 857 | break; 858 | } 859 | ++i; 860 | } 861 | if(i < path.Count){ 862 | path.RemoveRange(i,path.Count - i); 863 | } 864 | } 865 | /*public static List SharedNeighbors(this pos p,pos other,bool return_origins_if_adjacent){ 866 | List result = p.PositionsWithinDistance(1,!return_origins_if_adjacent); 867 | List others = other.PositionsWithinDistance(1,!return_origins_if_adjacent); 868 | result.RemoveWhere(x=>!others.Contains(x)); 869 | return result; 870 | }*/ 871 | public static float[] GetFloatValues(this Color color){ 872 | Color4 c = Colors.ConvertColor(color); 873 | return new float[]{c.R,c.G,c.B,c.A}; 874 | } 875 | public static float[] GetFloatValues(this Color4 c){ 876 | return new float[]{c.R,c.G,c.B,c.A}; 877 | } 878 | public static SkillType GetAssociatedSkill(this TileType t){ 879 | switch(t){ 880 | case TileType.COMBAT_SHRINE: 881 | return SkillType.COMBAT; 882 | case TileType.DEFENSE_SHRINE: 883 | return SkillType.DEFENSE; 884 | case TileType.MAGIC_SHRINE: 885 | return SkillType.MAGIC; 886 | case TileType.SPIRIT_SHRINE: 887 | return SkillType.SPIRIT; 888 | case TileType.STEALTH_SHRINE: 889 | return SkillType.STEALTH; 890 | default: 891 | return SkillType.NO_SKILL; 892 | } 893 | } 894 | public static SkillType GetAssociatedSkill(this SchismDungeonGenerator.CellType t){ 895 | switch(t){ 896 | case SchismDungeonGenerator.CellType.SpecialFeature1: 897 | return SkillType.COMBAT; 898 | case SchismDungeonGenerator.CellType.SpecialFeature2: 899 | return SkillType.DEFENSE; 900 | case SchismDungeonGenerator.CellType.SpecialFeature3: 901 | return SkillType.MAGIC; 902 | case SchismDungeonGenerator.CellType.SpecialFeature4: 903 | return SkillType.SPIRIT; 904 | case SchismDungeonGenerator.CellType.SpecialFeature5: 905 | return SkillType.STEALTH; 906 | default: 907 | return SkillType.NO_SKILL; 908 | } 909 | } 910 | } 911 | } 912 | -------------------------------------------------------------------------------- /Forays/MouseUI.cs: -------------------------------------------------------------------------------- 1 | /*Copyright (c) 2015-2016 Derrick Creamer 2 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation 3 | files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, 4 | distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 7 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 8 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.*/ 9 | using System; 10 | using System.Collections.Generic; 11 | using System.Drawing; 12 | using PosArrays; 13 | using Utilities; 14 | namespace Forays{ 15 | public class Button{ 16 | public ConsoleKey key; 17 | public ConsoleModifiers mods; 18 | public Rectangle rect; 19 | public int row{get{ return rect.Y; } } 20 | public int col{get{ return rect.X; } } 21 | public int height{get{ return rect.Height; } } 22 | public int width{get{ return rect.Width; } } 23 | public Button(ConsoleKey key_,bool alt,bool ctrl,bool shift,int row,int col,int height,int width){ 24 | key = key_; 25 | mods = (ConsoleModifiers)0; 26 | if(alt){ 27 | mods |= ConsoleModifiers.Alt; 28 | } 29 | if(ctrl){ 30 | mods |= ConsoleModifiers.Control; 31 | } 32 | if(shift){ 33 | mods |= ConsoleModifiers.Shift; 34 | } 35 | rect = new Rectangle(col,row,width,height); 36 | } 37 | } 38 | public enum MouseMode{Map,Inventory,Menu,ScrollableMenu,NameEntry,Targeting,Directional,YesNoPrompt}; 39 | public static class MouseUI{ 40 | public static bool AutomaticButtonsFromStrings = false; 41 | public static bool IgnoreMouseMovement = false; 42 | public static bool IgnoreMouseClicks = false; 43 | public static bool VisiblePath = true; 44 | private static List button_map = new List(); 45 | private static List mouse_mode = new List(); 46 | public static int MaxDescriptionBoxLength = 28; 47 | public static Button Highlighted = null; 48 | public static PhysicalObject[,] mouselook_objects = new PhysicalObject[Global.SCREEN_H,Global.SCREEN_W]; 49 | public static PhysicalObject mouselook_current_target = null; 50 | public static Rectangle mouselook_current_desc_area = Rectangle.Empty; 51 | public static List mouse_path = null; 52 | public static bool fire_arrow_hack = false; //hack, used to allow double-clicking [s]hoot to fire arrows. 53 | public static bool descend_hack = false; //hack, used to make double-clicking Descend [>] cancel the action. 54 | public static Button GetButton(int row,int col){ 55 | if(button_map.LastOrDefault() == null || row < 0 || col < 0 || row >= Global.SCREEN_H || col >= Global.SCREEN_W){ 56 | return null; 57 | } 58 | return button_map.Last()[row,col]; 59 | } 60 | public static MouseMode Mode{ 61 | get{ 62 | if(mouse_mode.Count == 0){ 63 | return MouseMode.Menu; 64 | } 65 | return mouse_mode[mouse_mode.Count-1]; 66 | } 67 | } 68 | public static Button[,] ButtonMap{ 69 | get{ 70 | if(button_map.LastOrDefault() == null){ 71 | button_map[button_map.Count-1] = new Button[Global.SCREEN_H,Global.SCREEN_W]; 72 | } 73 | return button_map[button_map.Count-1]; 74 | } 75 | } 76 | public static void PushButtonMap(){ 77 | button_map.Add(null); 78 | mouse_mode.Add(MouseMode.Menu); 79 | RemoveHighlight(); 80 | RemoveMouseover(); 81 | } 82 | public static void PushButtonMap(MouseMode mode){ 83 | button_map.Add(null); 84 | mouse_mode.Add(mode); 85 | RemoveHighlight(); 86 | RemoveMouseover(); 87 | } 88 | public static void PopButtonMap(){ 89 | RemoveHighlight(); 90 | RemoveMouseover(); 91 | button_map.RemoveLast(); 92 | mouse_mode.RemoveLast(); 93 | } 94 | public static void CreateButton(ConsoleKey key_,bool shifted,int row,int col,int height,int width){ 95 | Button[,] buttons = ButtonMap; 96 | if(buttons[row,col] == null){ //if there's already a button there, do nothing. 97 | Button b = new Button(key_,false,false,shifted,row,col,height,width); 98 | for(int i=row;i] 208 | CreateStatsButton(ConsoleKey.W,false,Global.SCREEN_H-4,1); //walk 209 | CreateStatsButton(ConsoleKey.OemPlus,false,Global.SCREEN_H-3,1); //options [=] 210 | CreateStatsButton(ConsoleKey.Q,false,Global.SCREEN_H-2,1); //quit 211 | CreateStatsButton(ConsoleKey.V,false,Global.SCREEN_H-1,1); //view more 212 | break; 213 | case 2: 214 | if(Global.Option(OptionType.HIDE_VIEW_MORE)){ 215 | UI.status_row_cutoff = Global.SCREEN_H - 1; 216 | } 217 | else{ 218 | UI.status_row_cutoff = Global.SCREEN_H - 2; 219 | CreateStatsButton(ConsoleKey.V,false,Global.SCREEN_H-1,1); //view more 220 | } 221 | break; 222 | } 223 | 224 | CreateMapButton(ConsoleKey.P,false,-3,3); 225 | CreatePlayerStatsButtons(); 226 | CreateButton(ConsoleKey.X,false,Global.SCREEN_H-2,Global.MAP_OFFSET_COLS,1,9); //explore 227 | CreateButton(ConsoleKey.T,false,Global.SCREEN_H-2,Global.MAP_OFFSET_COLS+14,1,7); //torch 228 | CreateButton(ConsoleKey.S,false,Global.SCREEN_H-2,Global.MAP_OFFSET_COLS+26,1,11); //shoot bow 229 | CreateButton(ConsoleKey.R,false,Global.SCREEN_H-2,Global.MAP_OFFSET_COLS+41,1,6); //rest 230 | CreateButton(ConsoleKey.Z,false,Global.SCREEN_H-2,Global.MAP_OFFSET_COLS+52,1,14); //cast spell 231 | CreateButton(ConsoleKey.I,false,Global.SCREEN_H-1,Global.MAP_OFFSET_COLS,1,11); //inventory 232 | CreateButton(ConsoleKey.E,false,Global.SCREEN_H-1,Global.MAP_OFFSET_COLS+14,1,11); //equipment 233 | CreateButton(ConsoleKey.C,false,Global.SCREEN_H-1,Global.MAP_OFFSET_COLS+26,1,11); //character 234 | CreateButton(ConsoleKey.M,false,Global.SCREEN_H-1,Global.MAP_OFFSET_COLS+41,1,5); //map 235 | CreateButton(ConsoleKey.F21,false,Global.SCREEN_H-1,Global.MAP_OFFSET_COLS+60,1,6); //menu 236 | } 237 | public static void CreatePlayerStatsButtons(){ 238 | if(Mode != MouseMode.Map) return; 239 | ConsoleKey[] keys = new ConsoleKey[]{ConsoleKey.C,ConsoleKey.E,ConsoleKey.M}; 240 | int[] rows = new int[]{0,UI.equipment_row,UI.depth_row}; 241 | int[] heights = new int[]{UI.equipment_row,UI.depth_row - UI.equipment_row,UI.status_row_start - UI.depth_row - 1}; 242 | if(MouseUI.GetButton(0,0) == null){ // if there's no button here, assume that there are no buttons in this area at all. 243 | for(int n=0;n<3;++n){ 244 | if(rows[n] + heights[n] > UI.status_row_cutoff){ 245 | return; 246 | } 247 | CreateStatsButton(keys[n],false,rows[n],heights[n]); 248 | } 249 | } 250 | else{ 251 | bool all_found = false; 252 | for(int n=0;n<3;++n){ 253 | if(heights[n] <= 0 || rows[n] + heights[n] > UI.status_row_cutoff){ 254 | break; 255 | } 256 | Button b = MouseUI.GetButton(rows[n],0); 257 | if(b != null && b.key == keys[n] && b.row == rows[n] && b.height == heights[n]){ //perfect match, keep it there. 258 | if(b.key == ConsoleKey.M){ 259 | all_found = true; 260 | } 261 | } 262 | else{ 263 | for(int i=rows[n];i"Foo" is pluralized as "Foos". "Pair~ of Foos" becomes "Pair of Foos" and is pluralized as "Pairs of Foos". 19 | /// "Foo Complex~~" is pluralized as "Foo Complexes". "Berry" is pluralized as "Berries". 20 | /// Default is to check for [AEIOU]. This bool is for exceptions to that rule. 21 | /// Marks uncountable nouns like "water", "courage", and "equipment". These names don't receive quantities or "a/an". 22 | /// Indistinct or unique names might not accept articles, like "something" or "Excalibur". 23 | /// Probably used for the name "you", to work correctly with verbs. 24 | public Name(string rawName, bool exceptionToAAnRule = false, bool uncountable = false, bool noArticles = false, bool secondPerson = false) { 25 | if(rawName.Contains("~")) { 26 | Singular = rawName.Replace("~",""); 27 | Plural = rawName.Replace("~~","es"); 28 | Plural = Plural.Replace("~","s"); 29 | } 30 | else { 31 | Singular = rawName; 32 | if(rawName.EndsWith("y") && !rawName.EndsWith("ay") && !rawName.EndsWith("ey") && !rawName.EndsWith("oy") && !rawName.EndsWith("uy")) { 33 | Plural = rawName.Substring(0, rawName.Length - 1) + "ies"; 34 | } 35 | else { 36 | if(rawName.EndsWith("sh") || rawName.EndsWith("ch") || rawName.EndsWith("s") || rawName.EndsWith("z") || rawName.EndsWith("x")) { 37 | Plural = rawName + "es"; 38 | } 39 | else { 40 | Plural = rawName + "s"; 41 | } 42 | } 43 | } 44 | this.uncountable = uncountable; 45 | this.noArticles = noArticles; 46 | this.secondPerson = secondPerson; 47 | SetAAn(Singular, exceptionToAAnRule); 48 | } 49 | /// Define the EXACT string used for the singular name. 50 | /// Define the EXACT string used for the plural name. 51 | /// Default is to check for [AEIOU]. This bool is for exceptions to that rule. 52 | /// Marks uncountable nouns like "water", "courage", and "equipment". These names don't receive quantities or "a/an". 53 | /// Indistinct or unique names might not accept articles, like "something" or "Excalibur". 54 | /// Probably used for the name "you", to work correctly with verbs. 55 | public Name(string singular, string plural, bool exceptionToAAnRule = false, bool uncountable = false, bool noArticles = false, bool secondPerson = false) { 56 | this.Singular = singular; 57 | this.Plural = plural; 58 | this.uncountable = uncountable; 59 | this.noArticles = noArticles; 60 | this.secondPerson = secondPerson; 61 | SetAAn(singular,exceptionToAAnRule); 62 | } 63 | private void SetAAn(string singular, bool exceptionToRule) { 64 | if(singular.Length > 0) { 65 | if(singular[0] == 'a' || singular[0] == 'e' || singular[0] == 'i' || singular[0] == 'o' || singular[0] == 'u') { 66 | usesAn = true; 67 | } 68 | } 69 | if(exceptionToRule) usesAn = !usesAn; 70 | } 71 | } 72 | 73 | public interface INamed { 74 | Name Name { get; } 75 | int Quantity { get; } 76 | Func GetExtraInfo { get; } 77 | } 78 | 79 | public class Named : INamed { //this class is for convenience; it isn't required for anything. 80 | public Name Name { get; set; } 81 | public int Quantity { get; set; } 82 | public Func GetExtraInfo { get; set; } 83 | 84 | public Named(string rawName, int qty = 1, Func getExtraInfo = null) : this(new Name(rawName), qty, getExtraInfo) { } 85 | public Named(Name name, int qty = 1, Func getExtraInfo = null) { 86 | Name = name; 87 | Quantity = qty; 88 | GetExtraInfo = getExtraInfo; 89 | } 90 | } 91 | 92 | public static class NameExtensions { 93 | public static string IsAre(this INamed n) { 94 | if(n.Quantity != 1 || n.Name.secondPerson) return "are"; 95 | else return "is"; 96 | } 97 | public static string ThisThese(this INamed n) { 98 | if(n.Quantity != 1) return "these"; 99 | else return "this"; 100 | } 101 | public static string GetName(this INamed n, params NameElement[] elements) => n.Name.GetName(n.Quantity, n.GetExtraInfo, elements); 102 | public static string GetName(this Name name, params NameElement[] elements) => name.GetName(1,null,elements); 103 | public static string GetName(this Name name, int qty, Func getExtraInfo, params NameElement[] elements) { 104 | string articleStr = null; 105 | string qtyStr = null; 106 | string nameStr = null; 107 | string extraStr = null; 108 | string verbStr = null; //verb or possessive, actually 109 | 110 | if(qty == 1 || name.uncountable) { 111 | nameStr = name.Singular; 112 | } 113 | else { 114 | nameStr = name.Plural; 115 | qtyStr = qty.ToString() + " "; 116 | } 117 | 118 | foreach(NameElement e in elements) { 119 | if(e.verb != null) { 120 | verbStr = " " + Verbs.Conjugate(e.verb, e.thirdPersonSingular, qty != 1, name.secondPerson); 121 | continue; 122 | } 123 | if(e == NameElement.The) { 124 | if(!name.noArticles) articleStr = "the "; 125 | continue; 126 | } 127 | if(e == NameElement.An) { 128 | if(!name.noArticles && qty == 1) { 129 | if(name.uncountable) articleStr = "some "; 130 | else articleStr = name.usesAn? "an " : "a "; 131 | } 132 | continue; 133 | } 134 | if(e == NameElement.Possessive) { 135 | if(name.secondPerson) verbStr = "r"; // this forms "your". Feels hacky but it works. 136 | else verbStr = "'s"; 137 | continue; 138 | } 139 | if(e == NameElement.Plural) { 140 | nameStr = name.Plural; 141 | continue; 142 | } 143 | if(e == NameElement.Qty) { 144 | if(!name.uncountable) qtyStr = qty.ToString() + " "; 145 | continue; 146 | } 147 | if(e == NameElement.NoQty) { 148 | qtyStr = null; 149 | continue; 150 | } 151 | if(e == NameElement.Extra) { 152 | extraStr = getExtraInfo?.Invoke(); 153 | continue; 154 | } 155 | } 156 | return articleStr + qtyStr + nameStr + extraStr + verbStr; 157 | } 158 | } 159 | 160 | public class NameElement { 161 | public string verb; 162 | public string thirdPersonSingular; 163 | /// For example, "attack". 164 | /// For example, "attacks". 165 | public NameElement(string verb = null,string thirdPersonSingular = null) { 166 | this.verb = verb; 167 | this.thirdPersonSingular = thirdPersonSingular; 168 | } 169 | /// 170 | public static NameElement Verb(string verb,string thirdPersonSingular = null) { 171 | return new NameElement(verb,thirdPersonSingular); 172 | } 173 | /// 174 | public static readonly NameElement The = new NameElement(); 175 | /// 176 | public static readonly NameElement An = new NameElement(); 177 | /// 178 | public static readonly NameElement Possessive = new NameElement(); 179 | /// 180 | public static readonly NameElement Plural = new NameElement(); 181 | /// 182 | public static readonly NameElement Qty = new NameElement(); //todo, Qty is currently only good for showing "1". Remove or keep? 183 | /// 184 | public static readonly NameElement NoQty = new NameElement(); 185 | /// 186 | public static readonly NameElement Extra = new NameElement(); 187 | /// 188 | public static readonly NameElement Are = new NameElement("are","is"); 189 | } 190 | 191 | public static class Verbs { 192 | /// For example, "attack". 193 | /// For example, "attacks". 194 | public static void Register(string verb, string thirdPersonSingular) { 195 | if(registered.ContainsKey(verb)) registered.Remove(verb); 196 | registered.Add(verb,thirdPersonSingular); 197 | } 198 | /// For example, "attack". 199 | /// For example, "attacks". 200 | public static string Conjugate(string verb, string thirdPersonSingular, bool plural, bool secondPerson) { 201 | if(plural || secondPerson) return verb; 202 | else { 203 | if(registered.ContainsKey(verb)) return registered[verb]; 204 | else { 205 | if(thirdPersonSingular != null) return thirdPersonSingular; // use this one if it has been provided 206 | else { // otherwise, use the default, which does *not* attempt to handle every single rule. 207 | if(verb.EndsWith("sh") || verb.EndsWith("ch") || verb.EndsWith("s") || verb.EndsWith("z") || verb.EndsWith("x")) { 208 | return verb + "es"; 209 | } 210 | if(verb.EndsWith("y")) { 211 | if(!verb.EndsWith("ay") && !verb.EndsWith("ey") && !verb.EndsWith("oy") && !verb.EndsWith("uy")) { 212 | return verb.Substring(0,verb.Length - 1) + "ies"; 213 | } 214 | } 215 | return verb + "s"; 216 | } 217 | } 218 | } 219 | } 220 | private static Dictionary registered = new Dictionary(); 221 | static Verbs(){ 222 | Register("do", "does"); //todo, i'm sure there are lots more 223 | } 224 | } 225 | } 226 | 227 | -------------------------------------------------------------------------------- /Forays/PosArray.cs: -------------------------------------------------------------------------------- 1 |  /*Copyright (c) 2013-2015 Derrick Creamer 2 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation 3 | files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, 4 | distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 7 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 8 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.*/ 9 | using System; 10 | using System.Collections; 11 | namespace PosArrays{ 12 | public struct pos{ //a generic position struct 13 | public int row; 14 | public int col; 15 | public pos(int r,int c){ 16 | row = r; 17 | col = c; 18 | } 19 | } 20 | public class PosArray{ //a 2D array with a position indexer and 1D indexer in addition to the usual 2D indexer 21 | public T[,] objs; 22 | public T this[int row,int col]{ 23 | get{ 24 | return objs[row,col]; 25 | } 26 | set{ 27 | objs[row,col] = value; 28 | } 29 | } 30 | public T this[pos p]{ 31 | get{ 32 | return objs[p.row,p.col]; 33 | } 34 | set{ 35 | objs[p.row,p.col] = value; 36 | } 37 | } 38 | public T this[int idx]{ 39 | get{ 40 | return objs[idx / objs.GetLength(1),idx % objs.GetLength(1)]; 41 | } 42 | set{ 43 | objs[idx / objs.GetLength(1),idx % objs.GetLength(1)] = value; 44 | } 45 | } 46 | public IEnumerator GetEnumerator(){ 47 | return objs.GetEnumerator(); 48 | } 49 | public PosArray(int rows,int cols){ 50 | objs = new T[rows,cols]; 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /Forays/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("Forays Into Norrendrin")] 9 | [assembly: AssemblyDescription("A roguelike game with deeply tactical combat.")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("Forays")] 13 | [assembly: AssemblyCopyright("Copyright © 2014")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("78b2f355-3feb-4612-a76a-6f9543c08ea2")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | // todo: don't forget to update this version too 36 | [assembly: AssemblyVersion("0.8.4.0")] 37 | [assembly: AssemblyFileVersion("1.0.0.0")] 38 | -------------------------------------------------------------------------------- /Forays/Spell.cs: -------------------------------------------------------------------------------- 1 | /*Copyright (c) 2011-2015 Derrick Creamer 2 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation 3 | files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, 4 | distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 7 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 8 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.*/ 9 | using System; 10 | namespace Forays{ 11 | public static class Spell{ 12 | public static int Tier(SpellType spell){ 13 | switch(spell){ 14 | case SpellType.RADIANCE: 15 | case SpellType.FORCE_PALM: 16 | case SpellType.DETECT_MOVEMENT: 17 | case SpellType.FLYING_LEAP: 18 | return 1; 19 | case SpellType.MERCURIAL_SPHERE: 20 | case SpellType.GREASE: 21 | case SpellType.BLINK: 22 | case SpellType.FREEZE: 23 | return 2; 24 | case SpellType.SCORCH: 25 | case SpellType.LIGHTNING_BOLT: 26 | case SpellType.MAGIC_HAMMER: 27 | case SpellType.PORTAL: 28 | return 3; 29 | case SpellType.PASSAGE: 30 | case SpellType.AMNESIA: 31 | case SpellType.STONE_SPIKES: 32 | case SpellType.SHADOWSIGHT: 33 | return 4; 34 | case SpellType.BLIZZARD: 35 | case SpellType.COLLAPSE: 36 | case SpellType.DOOM: 37 | case SpellType.TELEKINESIS: 38 | return 5; 39 | default: 40 | return 5; 41 | } 42 | } 43 | public static int FailRate(SpellType spell,int exhaustion){ 44 | return Math.Max(0,exhaustion - ((6-Tier(spell)) * 20)); //tier 5 spells have a 1% fail rate at 21% exhaustion...tier 4 at 41%...and so on. 45 | } 46 | public static string Name(SpellType spell){ 47 | switch(spell){ 48 | case SpellType.RADIANCE: 49 | return "Radiance"; 50 | case SpellType.FORCE_PALM: 51 | return "Force palm"; 52 | case SpellType.DETECT_MOVEMENT: 53 | return "Detect movement"; 54 | case SpellType.FLYING_LEAP: 55 | return "Flying leap"; 56 | case SpellType.MERCURIAL_SPHERE: 57 | return "Mercurial sphere"; 58 | case SpellType.GREASE: 59 | return "Grease"; 60 | case SpellType.BLINK: 61 | return "Blink"; 62 | case SpellType.FREEZE: 63 | return "Freeze"; 64 | case SpellType.SCORCH: 65 | return "Scorch"; 66 | case SpellType.LIGHTNING_BOLT: 67 | return "Lightning bolt"; 68 | case SpellType.MAGIC_HAMMER: 69 | return "Magic hammer"; 70 | case SpellType.PORTAL: 71 | return "Portal"; 72 | case SpellType.PASSAGE: 73 | return "Passage"; 74 | case SpellType.DOOM: 75 | return "Doom"; 76 | case SpellType.AMNESIA: 77 | return "Amnesia"; 78 | case SpellType.SHADOWSIGHT: 79 | return "Shadowsight"; 80 | case SpellType.BLIZZARD: 81 | return "Blizzard"; 82 | case SpellType.COLLAPSE: 83 | return "Collapse"; 84 | case SpellType.TELEKINESIS: 85 | return "Telekinesis"; 86 | case SpellType.STONE_SPIKES: 87 | return "Stone spikes"; 88 | default: 89 | return "unknown spell"; 90 | } 91 | } 92 | public static bool IsDamaging(SpellType spell){ 93 | switch(spell){ 94 | case SpellType.BLIZZARD: 95 | case SpellType.COLLAPSE: 96 | case SpellType.STONE_SPIKES: 97 | case SpellType.FORCE_PALM: 98 | case SpellType.DOOM: 99 | case SpellType.LIGHTNING_BOLT: 100 | case SpellType.MAGIC_HAMMER: 101 | case SpellType.MERCURIAL_SPHERE: 102 | case SpellType.RADIANCE: 103 | return true; 104 | } 105 | return false; 106 | } 107 | public static colorstring Description(SpellType spell){ 108 | switch(spell){ 109 | case SpellType.RADIANCE: 110 | return new colorstring("Light source brightens and deals 1d6 ",Color.Gray); 111 | case SpellType.FORCE_PALM: 112 | return new colorstring("Deals 1d6 damage, knocks target back ",Color.Gray); 113 | case SpellType.DETECT_MOVEMENT: 114 | return new colorstring("Nearby movement is revealed ",Color.Gray); 115 | case SpellType.FLYING_LEAP: 116 | return new colorstring("Fly and move at double speed briefly ",Color.Gray); 117 | case SpellType.MERCURIAL_SPHERE: 118 | return new colorstring("2d6, bounces to nearby foes 3 times ",Color.Gray); 119 | case SpellType.GREASE: 120 | return new colorstring("Creates a pool of oil on the floor ",Color.Gray); 121 | case SpellType.BLINK: 122 | return new colorstring("Teleport a short distance randomly ",Color.Gray); 123 | case SpellType.FREEZE: 124 | return new colorstring("Encase your target in ice ",Color.Gray); 125 | case SpellType.SCORCH: 126 | return new colorstring("Set your target on fire ",Color.Gray); 127 | case SpellType.LIGHTNING_BOLT: 128 | return new colorstring("3d6, jumps to other nearby foes ",Color.Gray); 129 | case SpellType.MAGIC_HAMMER: 130 | return new colorstring("Range 1, deals 4d6 damage and stuns ",Color.Gray); 131 | case SpellType.PORTAL: 132 | return new colorstring("Create linked teleportation portals ",Color.Gray); 133 | case SpellType.PASSAGE: 134 | return new colorstring("Travel to the other side of a wall ",Color.Gray); 135 | case SpellType.DOOM: 136 | return new colorstring("4d6 damage, inflicts vulnerability ",Color.Gray); 137 | case SpellType.AMNESIA: 138 | return new colorstring("An enemy forgets your presence ",Color.Gray); 139 | case SpellType.SHADOWSIGHT: 140 | return new colorstring("See farther and better in darkness ",Color.Gray); 141 | case SpellType.BLIZZARD: 142 | return new colorstring("Radius 5 burst, 5d6 and slows enemies",Color.Gray); 143 | case SpellType.TELEKINESIS: 144 | return new colorstring("Throw your target forcefully ",Color.Gray); 145 | case SpellType.COLLAPSE: 146 | return new colorstring("4d6, breaks walls & drops rubble",Color.Gray); 147 | //return new colorstring("Radius 1, breaks walls & drops rubble",Color.Gray); 148 | case SpellType.STONE_SPIKES: 149 | return new colorstring("Radius 2, 4d6 and creates stalagmites",Color.Gray); 150 | default: 151 | return new colorstring(" Unknown. ",Color.Gray); 152 | } 153 | } 154 | public static colorstring DescriptionWithIncreasedDamage(SpellType spell){ 155 | switch(spell){ 156 | case SpellType.RADIANCE: 157 | return new colorstring("Light source brightens and deals ",Color.Gray,"2d6 ",Color.Yellow); 158 | case SpellType.FORCE_PALM: 159 | return new colorstring("Deals ",Color.Gray,"2d6",Color.Yellow," damage, knocks target back ",Color.Gray); 160 | case SpellType.COLLAPSE: 161 | return new colorstring("4d6",Color.Yellow,", breaks walls & drops rubble",Color.Gray); 162 | case SpellType.STONE_SPIKES: 163 | return new colorstring("Radius 2, ",Color.Gray,"4d6",Color.Yellow," and creates stalagmites",Color.Gray); 164 | case SpellType.MERCURIAL_SPHERE: 165 | return new colorstring("3d6",Color.Yellow,", bounces to nearby foes 3 times ",Color.Gray); 166 | case SpellType.LIGHTNING_BOLT: 167 | return new colorstring("4d6",Color.Yellow,", jumps to other nearby foes ",Color.Gray); 168 | case SpellType.MAGIC_HAMMER: 169 | return new colorstring("Range 1, deals ",Color.Gray,"5d6",Color.Yellow," damage and stuns ",Color.Gray); 170 | case SpellType.DOOM: 171 | return new colorstring("5d6",Color.Yellow," damage, inflicts vulnerability ",Color.Gray); 172 | case SpellType.BLIZZARD: 173 | return new colorstring("Radius 5 burst, ",Color.Gray,"6d6",Color.Yellow," and slows enemies",Color.Gray); 174 | default: 175 | return Description(spell); 176 | } 177 | } 178 | } 179 | } 180 | 181 | -------------------------------------------------------------------------------- /Forays/highscore.txt: -------------------------------------------------------------------------------- 1 | High scores: 2 | -- 3 | -- 4 | -------------------------------------------------------------------------------- /Forays/options.txt: -------------------------------------------------------------------------------- 1 | Options: 2 | Any line that starts with [TtFf] and a space MUST be one of the valid options(or, in the 2nd part, one of the valid tutorial tips): 3 | no_wall_sliding autopickup top_row_movement never_display_tips always_reset_tips dark_gray_unseen 4 | f no_wall_sliding 5 | f autopickup 6 | f top_row_movement 7 | f confirm_before_resting 8 | f never_display_tips 9 | f always_reset_tips 10 | f dark_gray_unseen 11 | f disable_graphics 12 | -- Tracking which tutorial tips have been displayed: 13 | f movement 14 | f attacking 15 | f torch 16 | f fire 17 | f exhaustion 18 | f recovery 19 | f switchingequipment 20 | f rangedattacks 21 | f shrines 22 | f distributionofshrines 23 | f feats 24 | f activefeats 25 | f combat 26 | f defense 27 | f magic 28 | f spirit 29 | f stealth 30 | f findingconsumables 31 | f identifiedconsumables 32 | f unidentifiedconsumables 33 | f magictrinkets 34 | f spelltiers 35 | f spellfailure 36 | f castingwithoutmana 37 | f exhaustionandarmor 38 | f shinyplatearmor 39 | f heavyplatearmor 40 | f criticalhits 41 | f instantkills 42 | f dodging 43 | f fightingtheunseen 44 | f notrevealedbylight 45 | f traps 46 | f crackedwall 47 | f firepit 48 | f poppies 49 | f stoneslab 50 | f blastfungus 51 | f poolofrestoration 52 | f increasedspeed 53 | f stunned 54 | f frozen 55 | f slimed 56 | f oiled 57 | f vulnerable 58 | f silenced 59 | f confusion 60 | f bleeding 61 | f immobilized 62 | f acidified 63 | f afraid 64 | f grabbed 65 | f dulled 66 | f possessed 67 | f heavy 68 | f merciful 69 | f negated 70 | f stuck 71 | f infested 72 | f weakpoint 73 | f wornout 74 | f damaged 75 | f stoneform 76 | f vampirism 77 | f roots 78 | f brutishstrength 79 | f mysticmind 80 | -- 81 | -------------------------------------------------------------------------------- /Forays/packages.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /ForaysUtilityTests/ForaysUtilityTests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {90615D39-E969-4F26-A129-1D4EC7FE8577} 8 | Library 9 | Properties 10 | ForaysUtilityTests 11 | ForaysUtilityTests 12 | v4.5.2 13 | 512 14 | 15 | 16 | true 17 | full 18 | false 19 | bin\Debug\ 20 | DEBUG;TRACE 21 | prompt 22 | 4 23 | 24 | 25 | pdbonly 26 | true 27 | bin\Release\ 28 | TRACE 29 | prompt 30 | 4 31 | 32 | 33 | 34 | ..\packages\NUnit.3.0.1\lib\net45\nunit.framework.dll 35 | True 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | {392222ef-9eee-45f8-afae-260d9d06c4c9} 53 | Forays 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 70 | -------------------------------------------------------------------------------- /ForaysUtilityTests/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("ForaysUtilityTests")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("Microsoft")] 12 | [assembly: AssemblyProduct("ForaysUtilityTests")] 13 | [assembly: AssemblyCopyright("Copyright © Microsoft 2016")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("90615d39-e969-4f26-a129-1d4ec7fe8577")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /ForaysUtilityTests/StringWrapBufferTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using NUnit.Framework; 3 | using ForaysUtilities; 4 | using System.Collections.Generic; 5 | 6 | namespace ForaysUtilityTests { 7 | [TestFixture] public class StringWrapBufferTests { 8 | [TestCase] public void ConstructorAndArguments() { 9 | Assert.Throws(typeof(ArgumentOutOfRangeException),() => { new StringWrapBuffer(5,0); }); //maxLength less than 1 throws 10 | StringWrapBuffer buffer = new StringWrapBuffer(-3,1,null,new char[0]); 11 | Assert.AreEqual(buffer.MaxLength,1); 12 | Assert.AreEqual(buffer.MaxLines,-3); 13 | } 14 | [TestCase] public void AddContentsAndClear() { 15 | var buffer = new StringWrapBuffer(4,7); 16 | Assert.AreEqual(buffer.Contents.Count,0); //0 strings after creation 17 | Assert.AreEqual(buffer.Clear().Count,0); //0 strings after clear 18 | buffer.Add(null); 19 | buffer.Add(""); 20 | buffer.Add(" "); //leading discarded separators are removed from strings that begin a new line 21 | Assert.AreEqual(buffer.Contents.Count,0); //0 strings after empty adds 22 | buffer.Add("Hello. "); 23 | Assert.AreEqual(buffer.Contents.Count,1); //1 string after add 24 | Assert.AreEqual(buffer.Contents[0],"Hello. "); //first string matches input 25 | Assert.AreEqual(buffer.Clear()[0],"Hello. "); //first string of cleared contents matches input 26 | Assert.AreEqual(buffer.Contents.Count,0); //0 strings after clear 27 | 28 | } 29 | [TestCase] public void LineOverflow() { 30 | var buffer = new StringWrapBuffer(2,10); //with this constructor: space is discarded, hyphen is retained 31 | buffer.Add("Hello. Goodbye. "); 32 | Assert.AreEqual(buffer.Contents.Count,2); //2 strings after add & wrap 33 | Assert.AreEqual(buffer.Contents[0],"Hello."); //wrap should happen at edge of discarded characters 34 | Assert.AreEqual(buffer.Contents[1],"Goodbye. "); //spaces not used for wrapping should be unaffected 35 | 36 | buffer = new StringWrapBuffer(2,10,new char[] {'+'},null); //+ is retained 37 | buffer.Add("Hello.+Goodbye.+"); 38 | Assert.AreEqual(buffer.Contents.Count,2); //2 strings after add & wrap 39 | Assert.AreEqual(buffer.Contents[0],"Hello.+"); //chosen + should be retained 40 | 41 | buffer.Clear(); 42 | buffer.Add("abcdefghijklm"); 43 | Assert.AreEqual(buffer.Contents.Count,2); //2 strings after add & wrap 44 | Assert.AreEqual(buffer.Contents[0],"abcdefghij"); //max length used when no separators present 45 | Assert.AreEqual(buffer.Contents[1],"klm"); 46 | 47 | buffer = new StringWrapBuffer(-1,5); 48 | buffer.Add("abcde "); 49 | Assert.AreEqual(buffer.Contents.Count,1); //discarded spaces should not create new line 50 | Assert.AreEqual(buffer.Contents[0],"abcde"); 51 | 52 | buffer = new StringWrapBuffer(-1,20); 53 | buffer.Add("0 1 2 3 4 5 6 7 8 9 "); 54 | buffer.MaxLength = 12; 55 | Assert.AreEqual(buffer.Contents.Count,2); //should wrap onto 2nd line 56 | Assert.AreEqual(buffer.Contents[0],"0 1 2 3 4 5"); 57 | Assert.AreEqual(buffer.Contents[1],"6 7 8 9 "); 58 | buffer.MaxLength = 3; 59 | Assert.AreEqual(buffer.Contents.Count,3); //should discard spaces and end up with 3 lines 60 | Assert.AreEqual(buffer.Contents[0],"0 1 2 3 4 5"); //the MaxLength value of 3 should not affect previous lines 61 | Assert.AreEqual(buffer.Contents[1],"6 7"); 62 | Assert.AreEqual(buffer.Contents[2],"8 9"); //the trailing space should be discarded too 63 | 64 | buffer = new StringWrapBuffer(-1,1,null,new char[] {'!'}); //! is discarded 65 | buffer.Add("abc!!!!hijklm"); //expected lines: a b c h i j k l m 66 | Assert.AreEqual(buffer.Contents.Count,9); 67 | Assert.AreEqual(buffer.Contents[2],"c"); 68 | Assert.AreEqual(buffer.Contents[3],"h"); 69 | 70 | Assert.Throws(typeof(ArgumentOutOfRangeException),()=> { buffer.MaxLength = 0; }); 71 | } 72 | [TestCase] public void BufferOverflow() { 73 | var buffer = new StringWrapBuffer(1,4); 74 | buffer.Add("abcde"); 75 | Assert.AreEqual(buffer.Contents.Count,1); //only the overflow should remain 76 | Assert.AreEqual(buffer.Contents[0],"e"); 77 | 78 | List bufferOverflow = new List(); 79 | buffer.BufferFull += list => { 80 | foreach(string s in list) { 81 | bufferOverflow.Add(s); 82 | } 83 | }; 84 | buffer.Add("fghijk"); //adding atop the "e" 85 | Assert.AreEqual(buffer.Contents[0],"ijk"); //only the overflow should remain 86 | Assert.AreEqual(bufferOverflow.Count,1); 87 | Assert.AreEqual(bufferOverflow[0],"efgh"); //this list should have received the full buffer's contents 88 | 89 | buffer = new StringWrapBuffer(3,4); 90 | buffer.BufferFull += list => { 91 | foreach(string s in list) { 92 | bufferOverflow.Add(s); 93 | } 94 | }; 95 | bufferOverflow.Clear(); 96 | buffer.Add("one two four five "); 97 | Assert.AreEqual(buffer.Contents.Count,1); 98 | Assert.AreEqual(buffer.Contents[0],"five"); //only "five" should remain 99 | Assert.AreEqual(bufferOverflow.Count,3); 100 | Assert.AreEqual(bufferOverflow[0],"one"); 101 | Assert.AreEqual(bufferOverflow[2],"four"); 102 | 103 | buffer.MaxLines = -1; 104 | buffer.Clear(); 105 | bufferOverflow.Clear(); 106 | buffer.Add("one two four five "); 107 | buffer.MaxLines = 4; 108 | Assert.AreEqual(4,buffer.Contents.Count); 109 | buffer.MaxLines = 3; 110 | Assert.AreEqual(1,buffer.Contents.Count); 111 | } 112 | [TestCase] public void ReservedSpace() { 113 | var buffer = new StringWrapBuffer(3,10); 114 | buffer.ReservedSpace = 4; 115 | List bufferOverflow = new List(); 116 | buffer.BufferFull += list => { 117 | foreach(string s in list) { 118 | bufferOverflow.Add(s); 119 | } 120 | }; 121 | buffer.Add("Absolutely"); 122 | buffer.Add("positively"); 123 | buffer.Add("fantastic."); 124 | Assert.AreEqual(3,buffer.Contents.Count); 125 | buffer.Add(" "); 126 | Assert.AreEqual(3,buffer.Contents.Count); 127 | Assert.AreEqual(buffer.Contents[2],"fantastic."); 128 | buffer.Add("?"); 129 | Assert.AreEqual(3,bufferOverflow.Count); 130 | Assert.AreEqual("Absolutely",bufferOverflow[0]); 131 | Assert.AreEqual("fantas",bufferOverflow[2]); 132 | Assert.AreEqual(1,buffer.Contents.Count); 133 | Assert.AreEqual("tic. ?",buffer.Contents[0]); //the space is retained, now that it *isn't* the wrap location! 134 | 135 | buffer = new StringWrapBuffer(1,10); 136 | buffer.ReservedSpace = 4; 137 | bufferOverflow.Clear(); 138 | buffer.BufferFull += list => { 139 | foreach(string s in list) { 140 | bufferOverflow.Add(s); 141 | } 142 | }; 143 | buffer.Add("hey, great "); 144 | Assert.AreEqual(1,buffer.Contents.Count); 145 | Assert.AreEqual(0,bufferOverflow.Count); 146 | Assert.AreEqual("hey, great",buffer.Contents[0]); 147 | buffer.ConfirmReservedSpace(); 148 | Assert.AreEqual(1,bufferOverflow.Count); 149 | Assert.AreEqual("hey,",bufferOverflow[0]); 150 | Assert.AreEqual("great ",buffer.Contents[0]); 151 | 152 | Assert.Throws(typeof(ArgumentOutOfRangeException),()=>buffer.ReservedSpace = -1); 153 | } 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /ForaysUtilityTests/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Forays into Norrendrin is: 2 | Copyright (c) 2011-2015 Derrick Creamer 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | SOFTWARE. 21 | 22 | 23 | 24 | 25 | 26 | 27 | Forays into Norrendrin uses OpenTK. Licenses for it and its third-party code are as follows: 28 | 29 | The Open Toolkit library license 30 | 31 | Copyright (c) 2006 - 2014 Stefanos Apostolopoulos (stapostol@gmail.com) for the Open Toolkit library. 32 | 33 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 34 | 35 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 36 | 37 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 38 | 39 | Third parties 40 | 41 | OpenTK.Platform.Windows and OpenTK.Platform.X11 include portions of the Mono class library. These portions are covered by the following license: 42 | 43 | Copyright (c) 2004 Novell, Inc. 44 | 45 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 46 | 47 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 48 | 49 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 50 | 51 | OpenTK.Compatibility includes portions of the Tao Framework library (Tao.OpenGl, Tao.OpenAl and Tao.Platform.Windows.SimpleOpenGlControl). These portions are covered by the following license: 52 | 53 | Copyright (c) 2003-2007 Tao Framework Team 54 | http://www.taoframework.com 55 | All rights reserved. 56 | 57 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 58 | 59 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 60 | 61 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 62 | 63 | OpenTK.Half offers Half-to-Single and Single-to-Half conversions based on OpenEXR source code, which is covered by the following license: 64 | 65 | Copyright (c) 2002, Industrial Light & Magic, a division of Lucas Digital Ltd. LLC. All rights reserved. 66 | 67 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 68 | * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 69 | * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 70 | * Neither the name of Industrial Light & Magic nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. 71 | 72 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 73 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Forays into Norrendrin 2 | ====================== 3 | 4 | A streamlined roguelike with deeply tactical combat. 5 | 6 |
7 | 8 | [Read more about the game here.](http://forays.github.io/) 9 | 10 |
11 | 12 | #### How to build the console version: 13 | 14 | By default, the project will run in its own window, using 15 | OpenTK. To make it run in a terminal, follow these steps: 16 | 17 | 1. Open ConsoleForays.sln instead of Forays.sln. 18 | 2. Change the first line of the Main method (in Main.cs) 19 | by uncommenting "Screen.GLMode = false;". 20 | 3. Compile in Debug mode! Then run ConsoleForays.exe. 21 | 22 | --------------------------------------------------------------------------------