├── .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