├── .gitattributes ├── .gitignore ├── LICENSE ├── config ├── config.yml └── plugin.yml ├── javadoc ├── data.js ├── index.html ├── nav.js └── style.css ├── src └── com │ └── sucy │ └── enchant │ ├── EnchantmentAPI.java │ ├── api │ ├── Cooldowns.java │ ├── CustomEnchantment.java │ ├── EnchantPlugin.java │ ├── EnchantmentRegistry.java │ ├── Enchantments.java │ ├── GlowEffects.java │ ├── ItemSet.java │ ├── Settings.java │ └── Tasks.java │ ├── cmd │ ├── CmdAdd.java │ ├── CmdBook.java │ ├── CmdGraph.java │ ├── CmdReload.java │ ├── CmdRemove.java │ └── Commands.java │ ├── data │ ├── ConfigKey.java │ ├── Configuration.java │ ├── Enchantability.java │ ├── Path.java │ ├── Permission.java │ └── PlayerEquips.java │ ├── listener │ ├── AnvilListener.java │ ├── BaseListener.java │ ├── EnchantListener.java │ ├── FishingListener.java │ └── ItemListener.java │ ├── mechanics │ ├── EnchantResult.java │ ├── EnchantingMechanics.java │ └── EnchantmentMerger.java │ ├── skillapi │ ├── SkillAPIHook.java │ └── SkillEnchantment.java │ ├── util │ ├── LoreReader.java │ ├── RomanNumerals.java │ └── Utils.java │ └── vanilla │ ├── Vanilla.java │ ├── VanillaData.java │ └── VanillaEnchantment.java └── tst └── com └── sucy └── enchant ├── TestUtils.java ├── api ├── CooldownsTest.java ├── CustomEnchantmentTest.java ├── EnchantmentsTest.java └── ItemSetTest.java ├── cmd └── CmdGraphTest.java └── util ├── LoreReaderTest.java └── RomanNumeralsTest.java /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | *.sln merge=union 7 | *.csproj merge=union 8 | *.vbproj merge=union 9 | *.fsproj merge=union 10 | *.dbproj merge=union 11 | 12 | # Standard to msysgit 13 | *.doc diff=astextplain 14 | *.DOC diff=astextplain 15 | *.docx diff=astextplain 16 | *.DOCX diff=astextplain 17 | *.dot diff=astextplain 18 | *.DOT diff=astextplain 19 | *.pdf diff=astextplain 20 | *.PDF diff=astextplain 21 | *.rtf diff=astextplain 22 | *.RTF diff=astextplain 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | out/ 3 | *.iml 4 | modules/ 5 | 6 | ################# 7 | ## Eclipse 8 | ################# 9 | 10 | *.pydevproject 11 | .project 12 | .metadata 13 | bin/ 14 | tmp/ 15 | *.tmp 16 | *.bak 17 | *.swp 18 | *~.nib 19 | local.properties 20 | .classpath 21 | .settings/ 22 | .loadpath 23 | 24 | # External tool builders 25 | .externalToolBuilders/ 26 | 27 | # Locally stored "Eclipse launch configurations" 28 | *.launch 29 | 30 | # CDT-specific 31 | .cproject 32 | 33 | # PDT-specific 34 | .buildpath 35 | 36 | 37 | ################# 38 | ## Visual Studio 39 | ################# 40 | 41 | ## Ignore Visual Studio temporary files, build results, and 42 | ## files generated by popular Visual Studio add-ons. 43 | 44 | # User-specific files 45 | *.suo 46 | *.user 47 | *.sln.docstates 48 | 49 | # Build results 50 | 51 | [Dd]ebug/ 52 | [Rr]elease/ 53 | x64/ 54 | build/ 55 | [Bb]in/ 56 | [Oo]bj/ 57 | 58 | # MSTest test Results 59 | [Tt]est[Rr]esult*/ 60 | [Bb]uild[Ll]og.* 61 | 62 | *_i.c 63 | *_p.c 64 | *.ilk 65 | *.meta 66 | *.obj 67 | *.pch 68 | *.pdb 69 | *.pgc 70 | *.pgd 71 | *.rsp 72 | *.sbr 73 | *.tlb 74 | *.tli 75 | *.tlh 76 | *.tmp 77 | *.tmp_proj 78 | *.log 79 | *.vspscc 80 | *.vssscc 81 | .builds 82 | *.pidb 83 | *.log 84 | *.scc 85 | 86 | # Visual C++ cache files 87 | ipch/ 88 | *.aps 89 | *.ncb 90 | *.opensdf 91 | *.sdf 92 | *.cachefile 93 | 94 | # Visual Studio profiler 95 | *.psess 96 | *.vsp 97 | *.vspx 98 | 99 | # Guidance Automation Toolkit 100 | *.gpState 101 | 102 | # ReSharper is a .NET coding add-in 103 | _ReSharper*/ 104 | *.[Rr]e[Ss]harper 105 | 106 | # TeamCity is a build add-in 107 | _TeamCity* 108 | 109 | # DotCover is a Code Coverage Tool 110 | *.dotCover 111 | 112 | # NCrunch 113 | *.ncrunch* 114 | .*crunch*.local.xml 115 | 116 | # Installshield output folder 117 | [Ee]xpress/ 118 | 119 | # DocProject is a documentation generator add-in 120 | DocProject/buildhelp/ 121 | DocProject/Help/*.HxT 122 | DocProject/Help/*.HxC 123 | DocProject/Help/*.hhc 124 | DocProject/Help/*.hhk 125 | DocProject/Help/*.hhp 126 | DocProject/Help/Html2 127 | DocProject/Help/html 128 | 129 | # Click-Once directory 130 | publish/ 131 | 132 | # Publish Web Output 133 | *.Publish.xml 134 | *.pubxml 135 | 136 | # NuGet Packages Directory 137 | ## TODO: If you have NuGet Package Restore enabled, uncomment the next line 138 | #packages/ 139 | 140 | # Windows Azure Build Output 141 | csx 142 | *.build.csdef 143 | 144 | # Windows Store app package directory 145 | AppPackages/ 146 | 147 | # Others 148 | sql/ 149 | *.Cache 150 | ClientBin/ 151 | [Ss]tyle[Cc]op.* 152 | ~$* 153 | *~ 154 | *.dbmdl 155 | *.[Pp]ublish.xml 156 | *.pfx 157 | *.publishsettings 158 | 159 | # RIA/Silverlight projects 160 | Generated_Code/ 161 | 162 | # Backup & report files from converting an old project file to a newer 163 | # Visual Studio version. Backup files are not needed, because we have git ;-) 164 | _UpgradeReport_Files/ 165 | Backup*/ 166 | UpgradeLog*.XML 167 | UpgradeLog*.htm 168 | 169 | # SQL Server files 170 | App_Data/*.mdf 171 | App_Data/*.ldf 172 | 173 | ############# 174 | ## Windows detritus 175 | ############# 176 | 177 | # Windows image file caches 178 | Thumbs.db 179 | ehthumbs.db 180 | 181 | # Folder config file 182 | Desktop.ini 183 | 184 | # Recycle Bin used on file shares 185 | $RECYCLE.BIN/ 186 | 187 | # Mac crap 188 | .DS_Store 189 | 190 | 191 | ############# 192 | ## Python 193 | ############# 194 | 195 | *.py[co] 196 | 197 | # Packages 198 | *.egg 199 | *.egg-info 200 | dist/ 201 | build/ 202 | eggs/ 203 | parts/ 204 | var/ 205 | sdist/ 206 | develop-eggs/ 207 | .installed.cfg 208 | 209 | # Installer logs 210 | pip-log.txt 211 | 212 | # Unit test / coverage reports 213 | .coverage 214 | .tox 215 | 216 | #Translations 217 | *.mo 218 | 219 | #Mr Developer 220 | .mr.developer.cfg 221 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Steven 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /config/config.yml: -------------------------------------------------------------------------------- 1 | # Max number of enchantments someone can receive 2 | # when using an enchantment table 3 | max-enchantments: 3 4 | # 5 | # Max number of enchantments someone can receive 6 | # when using an anvil 7 | max-merged-enchantments: 5 8 | # 9 | # Whether or not to let players use colors when 10 | # renaming items in anvils 11 | colored-names-in-anvils: true 12 | # 13 | # Whether or not to allow normally non-enchantable 14 | # items to be enchanted (e.g. blaze rods). This 15 | # will cause them to show up as books when placed 16 | # in an enchanting table. 17 | non-enchantables: true 18 | # 19 | # Global level enchantments can be combined up to. 20 | # Any enchantments with a higher max level than this 21 | # will use their max level instead. 22 | global-anvil-level: 0 23 | # 24 | # Whether or not custom enchantments can be obtained 25 | # on items received while fishing 26 | custom-fishing: true 27 | # 28 | # The enchantment level used to enchant items received 29 | # while fishing. Requires "custom-fishing" to be enabled. 30 | fishing-enchanting-level: 30 -------------------------------------------------------------------------------- /config/plugin.yml: -------------------------------------------------------------------------------- 1 | name: EnchantmentAPI 2 | main: com.sucy.enchant.EnchantmentAPI 3 | version: 1.0.6 4 | depend: [MCCore] 5 | softdepend: [SkillAPI] 6 | api-version: 1.13 7 | 8 | permissions: 9 | EnchantmentAPI.list: 10 | description: basic list of enchantments 11 | default: true 12 | EnchantmentAPI.book: 13 | description: gives a book with enchantment descriptions 14 | default: true 15 | EnchantmentAPI.admin: 16 | description: applying enchantments 17 | default: op 18 | EnchantmentAPI.table: 19 | description: getting custom enchantments from the table 20 | default: true 21 | EnchantmentAPI.enchant: 22 | description: access to all enchantments 23 | default: true 24 | EnchantmentAPI.enchant.vanilla: 25 | description: access to all vanilla enchantments 26 | default: true -------------------------------------------------------------------------------- /javadoc/index.html: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Javadocs 6 | 7 | 8 | 9 | 10 | 11 |
12 |
13 |
14 |
15 |
16 |
17 | 18 | 19 | -------------------------------------------------------------------------------- /javadoc/nav.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var packageIndex = PACKAGES.length; 4 | var classIndex = -1; 5 | var targetElement = window.location.href.split('?')[0].split('#')[1]; 6 | var url = window.location.href.split('?')[0].split('#')[0]; 7 | var py, cy; 8 | 9 | // Initializes page elements when the page loads 10 | window.onload = function () { 11 | try { 12 | getIndices(); 13 | } 14 | catch (err) { } 15 | setupSideNavigation(); 16 | setupContent(); 17 | scrollToElement(targetElement); 18 | } 19 | 20 | // Gets the package and class indices from the window location 21 | function getIndices() { 22 | if (window.location.href.indexOf('?') < 0) return; 23 | var page = window.location.href.split('?')[1]; 24 | if (page.indexOf(';') > 0) { 25 | var split = page.split(';'); 26 | page = split[1]; 27 | split = split[0].split(':'); 28 | py = parseInt(split[0]); 29 | cy = parseInt(split[1]); 30 | setTimeout(function () { 31 | document.querySelector('#packageList').scrollTop = py; 32 | document.querySelector('#classList').scrollTop = cy; 33 | console.log(py + ", " + cy + " -> " 34 | + document.querySelector('#packageList').scrollTop + ", " 35 | + document.querySelector('#classList').scrollTop); 36 | }, 1); 37 | 38 | } 39 | if (page != '*') { 40 | var parts = page.split(':'); 41 | var i; 42 | for (i = 0; i < PACKAGES.length; i++) { 43 | if (PACKAGES[i] == parts[0]) { 44 | packageIndex = i; 45 | break; 46 | } 47 | } 48 | if (parts[1]) { 49 | classIndex = getClassId(parts[1]); 50 | } 51 | } 52 | } 53 | 54 | function linkElement(name, id) { 55 | if (!id) id = name; 56 | var a = document.createElement('a'); 57 | a.addEventListener('click', elementClicked); 58 | a['eId'] = id; 59 | a.innerHTML = name; 60 | return a; 61 | } 62 | 63 | function elementClicked() { 64 | window.location.href = url + '#' + this.eId + '?' + getScrollData() + PACKAGES[packageIndex] + ':' + getClass(classIndex).name; 65 | } 66 | 67 | function scrollToElement(element) { 68 | var target = document.querySelector('#' + element); 69 | if (target) { 70 | document.querySelector('#main').scrollTop = target.offsetTop; 71 | } 72 | } 73 | 74 | // Links the class to it's URL 75 | function linkClass(name) { 76 | var lt = name.indexOf('<'); 77 | if (lt > 0) { 78 | var container = document.createElement('span'); 79 | container.appendChild(linkClass(name.substr(0, lt))); 80 | container.appendChild(linkClass("<")); 81 | var comma = name.indexOf(","); 82 | if (comma > lt) { 83 | container.appendChild(linkClass(name.substring(lt + 1, comma))); 84 | container.appendChild(linkClass(", ")); 85 | container.appendChild(linkClass(name.substring(comma + 1, name.length - 1))); 86 | } 87 | else container.appendChild(linkClass(name.substring(lt + 1, name.length - 1))); 88 | container.appendChild(linkClass(">")); 89 | return container; 90 | } 91 | var link = linkClassList(name, INTERFACES); 92 | if (!link) link = linkClassList(name, CLASSES); 93 | if (!link) link = linkClassList(name, ENUMS); 94 | if (!link) link = linkClassList(name, ANNOTATIONS); 95 | if (!link) link = linkClassList(name, EXCEPTIONS); 96 | if (link) return link; 97 | var e = document.createElement('span'); 98 | e.innerHTML = name; 99 | return e; 100 | } 101 | 102 | // Searches through a list to find the URL of the class 103 | function linkClassList(name, list) { 104 | for (var i = 0; i < list.length; i++) { 105 | var subList = list[i]; 106 | for (var j = 0; j < subList.length; j++) { 107 | if (subList[j].name == name) { 108 | var l = document.createElement('a'); 109 | l.addEventListener('click', linkClick); 110 | l['linkKey'] = PACKAGES[i] + ':' + name; 111 | l.innerHTML = name; 112 | return l; 113 | } 114 | } 115 | } 116 | return false; 117 | } 118 | 119 | function linkClick() { 120 | window.location.href = url + '?' + getScrollData() + this.linkKey; 121 | } 122 | 123 | // Sets up the sidebar navigation 124 | function setupSideNavigation() { 125 | if (PACKAGES.length == 0) return; 126 | var pList = document.querySelector('#packageList'); 127 | var allLink = document.createElement('a'); 128 | allLink.innerHTML = 'All Classes'; 129 | allLink.className = 'packageLink'; 130 | allLink.packageIndex = PACKAGES.length; 131 | if (packageIndex == PACKAGES.length) allLink.className += ' selectedPackage'; 132 | allLink.onclick = linkClicked; 133 | pList.appendChild(allLink); 134 | addTag(pList, 'p', 'Packages'); 135 | for (var i = 0; i < PACKAGES.length; i++) { 136 | var link = document.createElement('a'); 137 | link.innerHTML = PACKAGES[i]; 138 | link.className = 'packageLink'; 139 | link.packageIndex = i 140 | if (i == packageIndex) link.className += ' selectedPackage'; 141 | link.onclick = linkClicked; 142 | pList.appendChild(link); 143 | } 144 | 145 | updateClasses(); 146 | } 147 | 148 | function linkClicked() { 149 | window.location.href = url + '?' + getScrollData() + 150 | (this.innerHTML == 'All Classes' ? '*' 151 | : this.className.charAt(0) != 'c' ? PACKAGES[this.packageIndex] 152 | : packageIndex == PACKAGES.length ? this.package + ':' + this.innerHTML 153 | : PACKAGES[packageIndex] + ':' + this.innerHTML); 154 | } 155 | 156 | function getScrollData() { 157 | return document.querySelector('#packageList').scrollTop + ':' 158 | + document.querySelector('#classList').scrollTop + ';'; 159 | } 160 | 161 | // Sets up the content in the center panel 162 | function setupContent() { 163 | if (packageIndex == PACKAGES.length) setupOverview(); 164 | else if (classIndex == -1) setupPackage(); 165 | else setupClass(); 166 | } 167 | 168 | // Sets up the content for the overview 169 | function setupOverview() { 170 | var content = getMainSection(); 171 | 172 | // General data 173 | if (NAME) addTag(content, 'h1', NAME); 174 | if (DESCRIPTION) addTag(content, 'p', DESCRIPTION); 175 | 176 | // Authors 177 | var row; 178 | if (AUTHORS.length > 0) { 179 | addTag(content, 'header', 'Authors'); 180 | var table = addTag(content, 'table'); 181 | for (var i = 0; i < AUTHORS.length; i++) { 182 | row = addTag(table, 'tr'); 183 | addTag(row, 'td', AUTHORS[i]); 184 | } 185 | } 186 | 187 | // Depends 188 | var row; 189 | if (DEPENDS.length > 0) { 190 | addTag(content, 'header', 'Dependencies'); 191 | var table = addTag(content, 'table'); 192 | for (var i = 0; i < DEPENDS.length; i++) { 193 | row = addTag(table, 'tr'); 194 | addTag(row, 'td', DEPENDS[i]); 195 | } 196 | } 197 | 198 | // Soft Depends 199 | var row; 200 | if (SOFTDEPENDS.length > 0) { 201 | addTag(content, 'header', 'Soft Dependencies'); 202 | var table = addTag(content, 'table'); 203 | for (var i = 0; i < SOFTDEPENDS.length; i++) { 204 | row = addTag(table, 'tr'); 205 | addTag(row, 'td', SOFTDEPENDS[i]); 206 | } 207 | } 208 | 209 | // Load Before 210 | var row; 211 | if (LOAD_BEFORE.length > 0) { 212 | addTag(content, 'header', 'Loads Before'); 213 | var table = addTag(content, 'table'); 214 | for (var i = 0; i < LOAD_BEFORE.length; i++) { 215 | row = addTag(table, 'tr'); 216 | addTag(row, 'td', LOAD_BEFORE[i]); 217 | } 218 | } 219 | 220 | // Package table 221 | addTag(content, 'header', 'Packages'); 222 | var table = addTag(content, 'table'); 223 | for (var i = 0; i < PACKAGES.length; i++) { 224 | row = addTag(table, 'tr'); 225 | var td = addTag(row, 'td', PACKAGES[i]); 226 | td.className = 'packageLink'; 227 | td.packageIndex = i; 228 | td.onclick = linkClicked; 229 | //addTag(row, 'td', 'Not set so this is placeholder'); 230 | } 231 | } 232 | 233 | // Sets up the content for a package 234 | function setupPackage() { 235 | var content = getMainSection(); 236 | addTag(content, 'h2', 'Package ' + PACKAGES[packageIndex]); 237 | 238 | addClassTable(content, 'Interfaces', INTERFACES[packageIndex]); 239 | addClassTable(content, 'Classes', CLASSES[packageIndex]); 240 | addClassTable(content, 'Enums', ENUMS[packageIndex]); 241 | addClassTable(content, 'Exceptions', EXCEPTIONS[packageIndex]); 242 | addClassTable(content, 'Annotations', ANNOTATIONS[packageIndex]); 243 | } 244 | 245 | // Sets up the content for a class 246 | function setupClass() { 247 | var content = getMainSection(); 248 | var c = getClass(classIndex); 249 | 250 | // Main information 251 | var header = c.type.charAt(0).toUpperCase() + c.type.slice(1) + ' ' + c.name; 252 | addTag(content, 'h2', header); 253 | addTag(content, 'code', c.scope + ' ' + (c.isStatic ? 'static ' : '') + (c.isFinal ? 'final ' : '') + (c.isAbstract ? 'abstract ' : '') + c.type + ' ' + c.name); 254 | if (c.ext) { 255 | var tag = addTag(content, 'code', 'extends '); 256 | tag.appendChild(linkClass(c.ext)); 257 | } 258 | if (c.impl.length > 0) { 259 | var tag = addTag(content, 'code', 'implements '); 260 | for (var k = 0; k < c.impl.length; k++) { 261 | tag.appendChild(linkClass(c.impl[k])); 262 | if (k != c.impl.length - 1) tag.appendChild(linkClass(', ')); 263 | } 264 | } 265 | addTag(content, 'hr'); 266 | addTag(content, 'p', c.description); 267 | 268 | var table, row, td, text, i, j; 269 | 270 | // Fields 271 | if (c.fields.length > 1) { 272 | if (c.type == 'enum') { 273 | addTag(content, 'header', 'Enum Constants'); 274 | table = addTag(content, 'table'); 275 | for (i = 1; i < c.fields.length; i++) { 276 | row = addTag(table, 'tr'); 277 | td = addTag(row, 'td'); 278 | td.appendChild(linkElement(c.fields[i].name)); 279 | } 280 | } 281 | else { 282 | addTag(content, 'header', 'Fields'); 283 | table = addTag(content, 'table'); 284 | for (i = 1; i < c.fields.length; i++) { 285 | row = addTag(table, 'tr'); 286 | td = addTag(row, 'td'); 287 | td.appendChild(linkClass(c.fields[i].type)); 288 | td = addTag(row, 'td'); 289 | td.appendChild(linkElement(c.fields[i].name)); 290 | } 291 | } 292 | } 293 | 294 | // Constructors 295 | if (c.constructors.length > 0) { 296 | addTag(content, 'header', 'Constructors'); 297 | table = addTag(content, 'table'); 298 | for (i = 0; i < c.constructors.length; i++) { 299 | row = addTag(table, 'tr'); 300 | td = addTag(row, 'td'); 301 | td.appendChild(linkElement(c.constructors[i].name, c.constructors[i].name + i)); 302 | addTag(td, 'span', '('); 303 | for (j = 0; j < c.constructors[i].params.length; j++) { 304 | td.appendChild(linkClass(c.constructors[i].params[j].type)); 305 | addTag(td, 'span', ' ' + c.constructors[i].params[j].name + (j != c.constructors[i].params.length - 1 ? ', ' : '')); 306 | } 307 | addTag(td, 'span', ')'); 308 | } 309 | } 310 | 311 | // Make sure there's at least one method and static method 312 | var oneMethod, oneStaticMethod; 313 | for (i = 0; i < c.methods.length; i++) { 314 | if (c.methods[i].isStatic) oneStaticMethod = true; 315 | else oneMethod = true; 316 | } 317 | 318 | // Methods 319 | if (oneMethod) { 320 | 321 | // Annotations 322 | if (c.type == 'annotation') { 323 | addTag(content, 'header', 'Optional Elements'); 324 | table = addTag(content, 'table'); 325 | for (i = 0; i < c.methods.length; i++) { 326 | if (c.methods[i].isStatic) continue; 327 | row = addTag(table, 'tr'); 328 | td = addTag(row, 'td'); 329 | td.appendChild(linkClass(c.methods[i].returnValue.type)); 330 | td = addTag(row, 'td'); 331 | td.appendChild(linkElement(c.methods[i].name, c.methods[i].name + i)); 332 | } 333 | } 334 | 335 | // Normal methods 336 | else { 337 | addTag(content, 'header', 'Methods'); 338 | table = addTag(content, 'table'); 339 | for (i = 0; i < c.methods.length; i++) { 340 | if (c.methods[i].isStatic) continue; 341 | row = addTag(table, 'tr'); 342 | td = addTag(row, 'td'); 343 | td.appendChild(linkClass(c.methods[i].returnValue.type)); 344 | td = addTag(row, 'td'); 345 | td.appendChild(linkElement(c.methods[i].name, c.methods[i].name + i)); 346 | addTag(td, 'span', '('); 347 | for (j = 0; j < c.methods[i].params.length; j++) { 348 | td.appendChild(linkClass(c.methods[i].params[j].type)); 349 | addTag(td, 'span', ' ' + c.methods[i].params[j].name + (j != c.methods[i].params.length - 1 ? ', ' : '')); 350 | } 351 | addTag(td, 'span', ')'); 352 | } 353 | } 354 | } 355 | 356 | // Static Methods 357 | if (oneStaticMethod) { 358 | addTag(content, 'header', 'Static Methods'); 359 | table = addTag(content, 'table'); 360 | for (i = 0; i < c.methods.length; i++) { 361 | if (!c.methods[i].isStatic) continue; 362 | row = addTag(table, 'tr'); 363 | var td = addTag(row, 'td'); 364 | td.appendChild(linkClass(c.methods[i].returnValue.type)); 365 | td = addTag(row, 'td'); 366 | td.appendChild(linkElement(c.methods[i].name, c.methods[i].name + i)); 367 | addTag(td, 'span', '('); 368 | for (j = 0; j < c.methods[i].params.length; j++) { 369 | td.appendChild(linkClass(c.methods[i].params[j].type)); 370 | addTag(td, 'span', ' ' + c.methods[i].params[j].name + (j != c.methods[i].params.length - 1 ? ', ' : '')); 371 | } 372 | addTag(td, 'span', ')'); 373 | } 374 | } 375 | 376 | /////////////////////////////////////////////// 377 | // // 378 | // Full Element Descriptions and Summaries // 379 | // // 380 | /////////////////////////////////////////////// 381 | 382 | // Fields 383 | if (c.fields.length > 1) { 384 | if (c.type == 'enum') { 385 | addTag(content, 'header', 'Enum Constants Detail'); 386 | for (i = 1; i < c.fields.length; i++) { 387 | var header = addTag(content, 'h3', c.fields[i].name); 388 | header.id = c.fields[i].name; 389 | addTag(header, 'code', 'public static final ' + c.name + ' ' + c.fields[i].name); 390 | addTag(header, 'p', c.fields[i].description); 391 | if (i != c.fields.length - 1) addTag(content, 'hr'); 392 | } 393 | } 394 | else { 395 | addTag(content, 'header', 'Fields Detail'); 396 | for (i = 1; i < c.fields.length; i++) { 397 | var header = addTag(content, 'h3', c.fields[i].name); 398 | header.id = c.fields[i].name; 399 | addTag(header, 'code', c.fields[i].scope + ' ' 400 | + (c.fields[i].isStatic ? 'static ' : '') 401 | + (c.fields[i].isFinal ? 'final ' : '') 402 | + (c.fields[i].isAbstract ? 'abstract ' : '') 403 | + c.fields[i].type + ' ' + c.fields[i].name); 404 | addTag(header, 'p', c.fields[i].description); 405 | if (i != c.fields.length - 1) addTag(content, 'hr'); 406 | } 407 | } 408 | } 409 | 410 | // Constructors 411 | if (c.constructors.length > 0) { 412 | addTag(content, 'header', 'Constructors Detail'); 413 | for (i = 0; i < c.constructors.length; i++) { 414 | var header = addTag(content, 'h3', c.name); 415 | header.id = c.name + i; 416 | text = c.constructors[i].scope + ' ' + c.name + '('; 417 | for (j = 0; j < c.constructors[i].params.length; j++) { 418 | text += c.constructors[i].params[j].type + ' ' + c.constructors[i].params[j].name; 419 | if (j != c.constructors[i].params.length - 1) text += ', '; 420 | } 421 | addTag(header, 'code', text + ')'); 422 | addTag(header, 'p', c.constructors[i].description); 423 | if (c.constructors[i].params.length > 0) { 424 | var subHeader = addTag(header, 'h4', 'params:'); 425 | for (j = 0; j < c.constructors[i].params.length; j++) { 426 | addTag(subHeader, 'p', '' + c.constructors[i].params[j].name + ' - ' + c.constructors[i].params[j].description); 427 | } 428 | } 429 | if (i != c.constructors.length - 1) addTag(content, 'hr'); 430 | } 431 | } 432 | 433 | // Methods 434 | if (oneMethod) { 435 | 436 | // Annotations 437 | if (c.type == "annotation") { 438 | for (i = 0; i < c.methods.length; i++) { 439 | if (c.methods[i].isStatic) continue; 440 | var header = addTag(content, 'h3', c.methods[i].name); 441 | header.id = c.methods[i].name + i; 442 | addTag(header, 'code', c.methods[i].scope + ' abstract ' + c.methods[i].returnValue.type + ' ' + c.methods[i].name); 443 | addTag(header, 'p', c.methods[i].description); 444 | var subHeader = addTag(header, 'h4', 'Returns:'); 445 | addTag(subHeader, 'p', c.methods[i].returnValue.description); 446 | addTag(content, 'hr'); 447 | } 448 | } 449 | 450 | // Normal methods 451 | else { 452 | addTag(content, 'header', 'Methods Detail'); 453 | for (i = 0; i < c.methods.length; i++) { 454 | if (c.methods[i].isStatic) continue; 455 | var header = addTag(content, 'h3', c.methods[i].name); 456 | header.id = c.methods[i].name + i; 457 | text = c.methods[i].scope + ' ' + c.methods[i].returnValue.type + ' ' + c.methods[i].name + '('; 458 | for (j = 0; j < c.methods[i].params.length; j++) { 459 | text += c.methods[i].params[j].type + ' ' + c.methods[i].params[j].name; 460 | if (j != c.methods[i].params.length - 1) text += ', '; 461 | } 462 | addTag(header, 'code', text + ')'); 463 | addTag(header, 'p', c.methods[i].description); 464 | if (c.methods[i].params.length > 0) { 465 | var subHeader = addTag(header, 'h4', 'Parameters:'); 466 | for (j = 0; j < c.methods[i].params.length; j++) { 467 | addTag(subHeader, 'p', '' + c.methods[i].params[j].name + ' - ' + c.methods[i].params[j].description); 468 | } 469 | } 470 | if (c.methods[i].returnValue.type != 'void') { 471 | var subHeader = addTag(header, 'h4', 'Returns:'); 472 | addTag(subHeader, 'p', c.methods[i].returnValue.description); 473 | } 474 | addTag(content, 'hr'); 475 | } 476 | } 477 | content.removeChild(content.lastChild); 478 | } 479 | 480 | // Static Methods 481 | if (oneStaticMethod) { 482 | addTag(content, 'header', 'Static Methods Detail'); 483 | for (i = 0; i < c.methods.length; i++) { 484 | if (!c.methods[i].isStatic) continue; 485 | var header = addTag(content, 'h3', c.methods[i].name); 486 | header.id = c.methods[i].name + i; 487 | text = c.methods[i].scope + ' ' + c.methods[i].returnValue.type + ' ' + c.methods[i].name + '('; 488 | for (j = 0; j < c.methods[i].params.length; j++) { 489 | text += c.methods[i].params[j].type + ' ' + c.methods[i].params[j].name; 490 | if (j != c.methods[i].params.length - 1) text += ', '; 491 | } 492 | addTag(header, 'code', text + ')'); 493 | addTag(header, 'p', c.methods[i].description); 494 | if (c.methods[i].params.length > 0) { 495 | var subHeader = addTag(header, 'h4', 'params:'); 496 | for (j = 0; j < c.methods[i].params.length; j++) { 497 | addTag(subHeader, 'p', '' + c.methods[i].params[j].name + ' - ' + c.methods[i].params[j].description); 498 | } 499 | } 500 | if (c.methods[i].returnValue.type != 'void') { 501 | var subHeader = addTag(header, 'h4', 'Returns:'); 502 | addTag(subHeader, 'p', c.methods[i].returnValue.description); 503 | } 504 | addTag(content, 'hr'); 505 | } 506 | content.removeChild(content.lastChild); 507 | } 508 | } 509 | 510 | // Retrieves the main HTML section 511 | function getMainSection() { 512 | return document.querySelector('#main'); 513 | } 514 | 515 | // Updates the list of classes in the navigation 516 | var classId; 517 | function updateClasses() { 518 | var cList = document.querySelector('#classList'); 519 | while (cList.hasChildNodes()) { 520 | cList.removeChild(cList.lastChild); 521 | } 522 | classId = 0; 523 | if (packageIndex == PACKAGES.length) { 524 | addTag(cList, 'p', 'Classes'); 525 | for (var i = 0; i < ALL_CLASSES.length; i++, classId++) { 526 | var parts = ALL_CLASSES[i].split(':'); 527 | var link = addTag(cList, 'a', parts[parts.length - 1]); 528 | link.className = 'classLink'; 529 | link.package = parts[0]; 530 | link.onclick = linkClicked; 531 | cList.appendChild(link); 532 | } 533 | } 534 | else { 535 | addClassList(cList, 'Interfaces', INTERFACES[packageIndex]); 536 | addClassList(cList, 'Classes', CLASSES[packageIndex]); 537 | addClassList(cList, 'Enums', ENUMS[packageIndex]); 538 | addClassList(cList, 'Exceptions', EXCEPTIONS[packageIndex]); 539 | addClassList(cList, 'Annotations', ANNOTATIONS[packageIndex]); 540 | } 541 | } 542 | 543 | function addClassList(element, title, list) { 544 | if (list.length == 0) return; 545 | addTag(element, 'p', title); 546 | for (var i = 0; i < list.length; i++, classId++) { 547 | var link = document.createElement('a'); 548 | link.innerHTML = list[i].name; 549 | link.className = 'classLink'; 550 | if (classId == classIndex) { 551 | link.className += ' selectedClass'; 552 | } 553 | link.onclick = linkClicked; 554 | element.appendChild(link); 555 | } 556 | } 557 | 558 | function addClassTable(element, title, list) { 559 | if (list.length == 0) return; 560 | addTag(element, 'header', title); 561 | var table = addTag(element, 'table'); 562 | var row; 563 | for (var i = 0; i < list.length; i++, classId++) { 564 | row = addTag(table, 'tr'); 565 | var link = addTag(row, 'td', list[i].name); 566 | link.className = 'classLink'; 567 | link.onclick = linkClicked; 568 | var desc = list[i].description; 569 | var copyright = desc.indexOf('©'); 570 | var space = desc.indexOf(' ', copyright + 10); 571 | if (copyright > 0 && space > 0) desc = desc.substr(space + 1); 572 | else if (copyright > 0) desc = 'No description provided'; 573 | while (desc.indexOf('
') == 0) desc = desc.substr(4); 574 | var desc = addTag(row, 'td', desc.split('

')[0].replace('

', '').split('. ')[0] + '.'); 575 | link.style.maxHeight = link.style.height = (desc.clientHeight - 14) + 'px'; 576 | } 577 | } 578 | 579 | function getClassId(name) { 580 | classId = 0; 581 | var id = checkClassId(name, INTERFACES[packageIndex]); 582 | if (id == -1) id = checkClassId(name, CLASSES[packageIndex]); 583 | if (id == -1) id = checkClassId(name, ENUMS[packageIndex]); 584 | if (id == -1) id = checkClassId(name, EXCEPTIONS[packageIndex]); 585 | if (id == -1) id = checkClassId(name, ANNOTATIONS[packageIndex]); 586 | return id; 587 | } 588 | 589 | function getClass(id) { 590 | classId = 0; 591 | var valid = checkClass(id, INTERFACES[packageIndex]); 592 | if (!valid) valid = checkClass(id, CLASSES[packageIndex]); 593 | if (!valid) valid = checkClass(id, ENUMS[packageIndex]); 594 | if (!valid) valid = checkClass(id, EXCEPTIONS[packageIndex]); 595 | if (!valid) valid = checkClass(id, ANNOTATIONS[packageIndex]); 596 | return valid; 597 | } 598 | 599 | function checkClassId(name, list) { 600 | for (var i = 0; i < list.length; i++, classId++) { 601 | if (list[i].name == name) return classId; 602 | } 603 | return -1; 604 | } 605 | 606 | function checkClass(id, list) { 607 | if (classId + list.length <= id) { 608 | classId += list.length; 609 | return false; 610 | } 611 | else return list[id - classId]; 612 | } 613 | 614 | // Adds a title to the element using the 'p' tag 615 | function addTag(element, tag, text) { 616 | var p = document.createElement(tag); 617 | if (text) p.innerHTML = text; 618 | element.appendChild(p); 619 | return p; 620 | } 621 | -------------------------------------------------------------------------------- /javadoc/style.css: -------------------------------------------------------------------------------- 1 | * { 2 | padding: 0; 3 | margin: 0; 4 | } 5 | 6 | a, a:visited { 7 | color: #0ce3ac; 8 | display: inline-block; 9 | padding: 1px 3px; 10 | text-decoration: none; 11 | cursor: pointer; 12 | border-radius: 5px; 13 | } 14 | 15 | a:hover { 16 | color: white; 17 | background: #008966; 18 | } 19 | 20 | body { 21 | overflow: hidden; 22 | background-color: #333; 23 | color: White; 24 | height: 100%; 25 | width: 100%; 26 | } 27 | 28 | html { 29 | height: 100%; 30 | width: 100%; 31 | } 32 | 33 | /*********** 34 | * General * 35 | ***********/ 36 | 37 | h1 { 38 | font-weight: bold; 39 | font-size: 42px; 40 | padding: 10px; 41 | } 42 | 43 | h2 44 | { 45 | font-weight: bold; 46 | font-size: 36px; 47 | padding: 10px; 48 | } 49 | 50 | h3 51 | { 52 | font-weight: bold; 53 | font-size: 24px; 54 | padding: 10px; 55 | } 56 | 57 | h4 58 | { 59 | font-size: 20px; 60 | padding-left: 30px; 61 | } 62 | 63 | header 64 | { 65 | background-color: #0d58a6; 66 | font-size: 25px; 67 | font-weight: bold; 68 | text-align: left; 69 | padding: 3px 10px; 70 | border: 2px solid #666; 71 | } 72 | 73 | p { 74 | font-size: 25px; 75 | font-weight: bold; 76 | padding: 10px 9px; 77 | } 78 | 79 | table 80 | { 81 | width: 100%; 82 | border: 2px solid #666; 83 | border-collapse: collapse; 84 | margin-bottom: 20px; 85 | border-top: none; 86 | } 87 | 88 | td 89 | { 90 | font-size: 18px; 91 | font-weight: normal; 92 | padding: 6px 5px; 93 | background-color: #222; 94 | border: 2px solid #666; 95 | border-radius: 10px; 96 | margin: 2px; 97 | } 98 | 99 | td p 100 | { 101 | font-size: 18px; 102 | font-weight: normal; 103 | padding: 0; 104 | } 105 | 106 | ul 107 | { 108 | list-style-position: inside; 109 | } 110 | 111 | ul li 112 | { 113 | margin-bottom: 10px; 114 | } 115 | 116 | /*********** 117 | * Classes * 118 | ***********/ 119 | 120 | .inline { 121 | display: inline-block; 122 | } 123 | 124 | .packageLink, .packageLink:visited, .classLink, .classLink:visited { 125 | color: white; 126 | display: block; 127 | margin: 2px; 128 | padding: 5px; 129 | background-color: #222; 130 | border: none; 131 | border-radius: 0; 132 | font-size: 18px; 133 | overflow: hidden; 134 | transition: background-color 500ms; 135 | cursor: pointer; 136 | text-decoration: none; 137 | } 138 | 139 | .packageLink:hover, .classLink:hover { 140 | background-color: #0c9; 141 | color: white; 142 | text-decoration: none; 143 | } 144 | 145 | .selectedPackage, .selectedPackage:visited, .selectedPackage:hover, .selectedClass, .selectedClass:visited, .selectedClass:hover { 146 | background-color: #008966; 147 | color: white; 148 | text-decoration: none; 149 | cursor: default; 150 | } 151 | 152 | /*************** 153 | * Identifiers * 154 | ***************/ 155 | 156 | #leftPanel 157 | { 158 | position: absolute; 159 | width: 250px; 160 | height: 100%; 161 | top: 0; 162 | left: 0; 163 | } 164 | 165 | #packageList { 166 | border-bottom: 2px solid #666; 167 | height: calc(38% - 2px); 168 | overflow-x: auto; 169 | overflow-y: scroll; 170 | } 171 | 172 | #classList { 173 | height: 62%; 174 | overflow-x: auto; 175 | overflow-y: scroll; 176 | } 177 | 178 | #main 179 | { 180 | width: calc(100% - 250px); 181 | max-width: 100%; 182 | height: 100%; 183 | max-height: 100%; 184 | padding-left: 250px; 185 | overflow-y: auto; 186 | } 187 | 188 | #main p 189 | { 190 | font-size: 18px; 191 | font-weight: normal; 192 | } 193 | 194 | #main code 195 | { 196 | font-size: 16px; 197 | padding-left: 10px; 198 | padding-bottom: 15px; 199 | display: block; 200 | max-width: 100%; 201 | } 202 | 203 | #main h4 p 204 | { 205 | padding: 3px 0; 206 | padding-left: 20px; 207 | 208 | } -------------------------------------------------------------------------------- /src/com/sucy/enchant/EnchantmentAPI.java: -------------------------------------------------------------------------------- 1 | package com.sucy.enchant; 2 | 3 | import com.rit.sucy.commands.CommandManager; 4 | import com.sucy.enchant.api.CustomEnchantment; 5 | import com.sucy.enchant.api.EnchantPlugin; 6 | import com.sucy.enchant.api.EnchantmentRegistry; 7 | import com.sucy.enchant.api.Enchantments; 8 | import com.sucy.enchant.cmd.Commands; 9 | import com.sucy.enchant.data.ConfigKey; 10 | import com.sucy.enchant.data.Configuration; 11 | import com.sucy.enchant.data.Enchantability; 12 | import com.sucy.enchant.listener.AnvilListener; 13 | import com.sucy.enchant.listener.BaseListener; 14 | import com.sucy.enchant.listener.EnchantListener; 15 | import com.sucy.enchant.listener.FishingListener; 16 | import com.sucy.enchant.listener.ItemListener; 17 | import com.sucy.enchant.skillapi.SkillAPIHook; 18 | import com.sucy.enchant.vanilla.VanillaData; 19 | import org.bukkit.Bukkit; 20 | import org.bukkit.event.HandlerList; 21 | import org.bukkit.plugin.Plugin; 22 | import org.bukkit.plugin.java.JavaPlugin; 23 | 24 | import java.util.ArrayList; 25 | import java.util.Collection; 26 | import java.util.HashMap; 27 | import java.util.List; 28 | import java.util.Map; 29 | import java.util.Objects; 30 | 31 | /** 32 | * EnchantmentAPI © 2017 33 | * com.sucy.enchant.EnchantmentAPI 34 | */ 35 | public class EnchantmentAPI extends JavaPlugin implements EnchantmentRegistry { 36 | 37 | private static final Map ENCHANTMENTS = new HashMap<>(); 38 | 39 | private final List listeners = new ArrayList<>(); 40 | 41 | private static EnchantmentAPI enabled; 42 | 43 | @Override 44 | public void onEnable() { 45 | if (enabled != null) throw new IllegalStateException("Cannot enable multiple times!"); 46 | enabled = this; 47 | 48 | Configuration.reload(this); 49 | Enchantability.init(this); 50 | 51 | registerEnchantments(); 52 | register(new ItemListener(), true); 53 | register(new EnchantListener(), true); 54 | register(new AnvilListener(), true); 55 | register(new FishingListener(), Configuration.using(ConfigKey.CUSTOM_FISHING)); 56 | Commands.init(this); 57 | } 58 | 59 | @Override 60 | public void onDisable() { 61 | if (enabled == null) throw new IllegalStateException("Plugin not enabled!"); 62 | enabled = null; 63 | 64 | CommandManager.unregisterCommands(this); 65 | ENCHANTMENTS.clear(); 66 | listeners.forEach(listener -> listener.cleanUp(this)); 67 | listeners.clear(); 68 | HandlerList.unregisterAll(this); 69 | Enchantments.clearAllEquipmentData(); 70 | } 71 | 72 | private void register(final BaseListener listener, final boolean condition) { 73 | if (condition) { 74 | listeners.add(listener); 75 | listener.init(this); 76 | getServer().getPluginManager().registerEvents(listener, this); 77 | } 78 | } 79 | 80 | /** 81 | * @param name enchantment name 82 | * @return true if the enchantment is registered successfully, false otherwise 83 | */ 84 | public static boolean isRegistered(final String name) { 85 | return name != null && ENCHANTMENTS.containsKey(name.toLowerCase()); 86 | } 87 | 88 | /** 89 | * @param name name of the enchantment (not case-sensitive) 90 | * @return enchantment with the provided name 91 | */ 92 | public static CustomEnchantment getEnchantment(final String name) { 93 | return name == null ? null : ENCHANTMENTS.get(name.toLowerCase()); 94 | } 95 | 96 | /** 97 | * @return collection of all registered enchantments including vanilla enchantments 98 | */ 99 | public static Collection getRegisteredEnchantments() { 100 | return ENCHANTMENTS.values(); 101 | } 102 | 103 | /** 104 | * Registers enchantments with the API 105 | * 106 | * @param enchantments enchantments to register 107 | */ 108 | @Override 109 | public void register(final CustomEnchantment... enchantments) { 110 | for (final CustomEnchantment enchantment : enchantments) { 111 | final String key = enchantment.getName().toLowerCase(); 112 | if (ENCHANTMENTS.containsKey(key)) { 113 | getLogger().warning("Duplicate enchantment name \"" + enchantment.getName() + "\" was found"); 114 | continue; 115 | } 116 | 117 | Objects.requireNonNull(enchantment, "Cannot register a null enchantment"); 118 | enchantment.load(this); 119 | enchantment.save(this); 120 | ENCHANTMENTS.put(key, enchantment); 121 | } 122 | } 123 | 124 | private void registerEnchantments() { 125 | for (final VanillaData vanillaData : VanillaData.values()) { 126 | if (vanillaData.doesExist()) { 127 | register(vanillaData.getEnchantment()); 128 | } 129 | } 130 | 131 | for (final Plugin plugin : Bukkit.getPluginManager().getPlugins()) { 132 | if (plugin instanceof EnchantPlugin) { 133 | try { 134 | ((EnchantPlugin) plugin).registerEnchantments(this); 135 | } catch (final Exception ex) { 136 | getLogger().warning(plugin.getName() + " failed to register enchantments. Send the error to the author."); 137 | ex.printStackTrace(); 138 | } 139 | } 140 | } 141 | 142 | SkillAPIHook.getEnchantments(this).forEach(this::register); 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /src/com/sucy/enchant/api/Cooldowns.java: -------------------------------------------------------------------------------- 1 | package com.sucy.enchant.api; 2 | 3 | import org.bukkit.entity.LivingEntity; 4 | 5 | import java.time.Clock; 6 | import java.util.HashMap; 7 | import java.util.Map; 8 | 9 | /** 10 | * EnchantmentAPI © 2017 11 | * com.sucy.enchant.api.Cooldowns 12 | * 13 | * Helper class for managing cooldowns for enchantments 14 | */ 15 | public class Cooldowns { 16 | private static final String COOLDOWN = "cooldown"; 17 | 18 | private static final Map cooldowns = new HashMap<>(); 19 | 20 | private static final Clock clock = Clock.systemUTC(); 21 | 22 | /** 23 | * Configures the cooldown for an enchantment 24 | * 25 | * @param settings settings of the enchantment 26 | * @param base base value to configure 27 | * @param scale scaling value to configure 28 | */ 29 | public static void configure(final Settings settings, final double base, final double scale) { 30 | settings.set(COOLDOWN, base, scale); 31 | } 32 | 33 | /** 34 | * Computes the number of seconds left on the enchantment's cooldown, 35 | * rounded up to the nearest second. 36 | * 37 | * @param enchant enchantment 38 | * @param user player using the enchantment 39 | * @param settings settings of the enchantment 40 | * @param level enchantment level 41 | * @return seconds left 42 | */ 43 | public static int secondsLeft(final CustomEnchantment enchant, final LivingEntity user, final Settings settings, final int level) { 44 | final String key = makeKey(enchant, user); 45 | final long time = cooldowns.getOrDefault(key, 0L); 46 | return (int)Math.ceil(settings.get(COOLDOWN, level) - (clock.millis() - time) / 1000.0); 47 | } 48 | 49 | /** 50 | * Checks whether or not the enchantment is on cooldown 51 | * 52 | * @param enchant enchantment to check 53 | * @param user player using the enchantment 54 | * @param settings settings of the enchantment 55 | * @param level enchantment level 56 | * @return true if on cooldown, false otherwise 57 | */ 58 | public static boolean onCooldown(final CustomEnchantment enchant, final LivingEntity user, final Settings settings, final int level) { 59 | return secondsLeft(enchant, user, settings, level) > 0; 60 | } 61 | 62 | /** 63 | * Starts the cooldown of an enchantment 64 | * 65 | * @param enchant enchantment to start for 66 | * @param user player using the enchantment 67 | */ 68 | public static void start(final CustomEnchantment enchant, final LivingEntity user) { 69 | final String key = makeKey(enchant, user); 70 | cooldowns.put(key, clock.millis()); 71 | } 72 | 73 | /** 74 | * Reduces the remaining time of an enchantment's cooldown. 75 | * 76 | * @param enchant enchantment to reduce the time for 77 | * @param user player using the enchantment 78 | * @param millis time to reduce by in milliseconds 79 | */ 80 | public static void reduce(final CustomEnchantment enchant, final LivingEntity user, final long millis) { 81 | final String key = makeKey(enchant, user); 82 | cooldowns.computeIfPresent(key, (k, time) -> time - millis); 83 | } 84 | 85 | private static String makeKey(final CustomEnchantment enchant, final LivingEntity user) { 86 | return enchant.getName() + user.getUniqueId(); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/com/sucy/enchant/api/CustomEnchantment.java: -------------------------------------------------------------------------------- 1 | package com.sucy.enchant.api; 2 | 3 | import com.google.common.collect.ImmutableList; 4 | import com.rit.sucy.config.CommentedConfig; 5 | import com.rit.sucy.config.parse.DataSection; 6 | import com.rit.sucy.text.TextFormatter; 7 | import com.sucy.enchant.EnchantmentAPI; 8 | import com.sucy.enchant.data.Permission; 9 | import com.sucy.enchant.util.LoreReader; 10 | import org.apache.commons.lang.Validate; 11 | import org.bukkit.Material; 12 | import org.bukkit.block.Block; 13 | import org.bukkit.enchantments.Enchantment; 14 | import org.bukkit.entity.LivingEntity; 15 | import org.bukkit.entity.Player; 16 | import org.bukkit.event.block.BlockEvent; 17 | import org.bukkit.event.entity.EntityDamageByEntityEvent; 18 | import org.bukkit.event.entity.EntityDamageEvent; 19 | import org.bukkit.event.entity.ProjectileLaunchEvent; 20 | import org.bukkit.event.player.PlayerInteractEntityEvent; 21 | import org.bukkit.event.player.PlayerInteractEvent; 22 | import org.bukkit.inventory.ItemStack; 23 | import org.bukkit.inventory.meta.ItemMeta; 24 | import org.bukkit.permissions.Permissible; 25 | 26 | import java.util.ArrayList; 27 | import java.util.Arrays; 28 | import java.util.HashMap; 29 | import java.util.HashSet; 30 | import java.util.List; 31 | import java.util.Map; 32 | import java.util.Objects; 33 | import java.util.Set; 34 | import java.util.stream.Collectors; 35 | 36 | import static com.sucy.enchant.util.Utils.isPresent; 37 | 38 | /** 39 | * EnchantmentAPI © 2017 40 | * com.sucy.enchant.api.CustomEnchantment 41 | */ 42 | public abstract class CustomEnchantment implements Comparable { 43 | 44 | /** 45 | * Default conflict group for enchantments. Using this group causes the 46 | * enchantment not to conflict with any other enchantments. 47 | */ 48 | public static final String DEFAULT_GROUP = "Default"; 49 | 50 | private final Map materialWeights = new HashMap<>(); 51 | 52 | private final Set naturalItems = new HashSet<>(); 53 | private final Set anvilItems = new HashSet<>(); 54 | 55 | private String key; 56 | private String name; 57 | private String description; 58 | 59 | private String group; 60 | private boolean enabled; 61 | private boolean tableEnabled; 62 | private boolean stacks; 63 | private double enchantLevelScaleFactor; 64 | private double minEnchantingLevel; 65 | private double enchantLevelBuffer; 66 | private double weight; 67 | private int maxLevel; 68 | private int maxTableLevel; 69 | private int combineCostPerLevel; 70 | 71 | private boolean setFactors = false; 72 | 73 | protected Settings settings = new Settings(); 74 | 75 | protected CustomEnchantment(final String name, final String description) { 76 | Validate.notEmpty(name, "The name must be present and not empty"); 77 | Validate.notEmpty(description, "The description must be present and not empty"); 78 | 79 | this.key = name.trim(); 80 | this.name = name.trim(); 81 | this.description = description.trim(); 82 | 83 | enabled = true; 84 | tableEnabled = true; 85 | group = DEFAULT_GROUP; 86 | maxLevel = 1; 87 | maxTableLevel = 1; 88 | minEnchantingLevel = 1; 89 | enchantLevelScaleFactor = 60; 90 | enchantLevelBuffer = 0; 91 | stacks = false; 92 | combineCostPerLevel = 1; 93 | 94 | weight = 5.0; 95 | } 96 | 97 | // ---- Getters/Setters ---- // 98 | 99 | /** 100 | * @return name of the enchantment that shows up in the lore 101 | */ 102 | public String getName() { 103 | return name; 104 | } 105 | 106 | /** 107 | * @return details for the enchantment to show in the details book 108 | */ 109 | public String getDescription() { 110 | return description; 111 | } 112 | 113 | /** 114 | * @return whether or not having the enchantment on multiple items stacks their effects 115 | */ 116 | public boolean canStack() { 117 | return stacks; 118 | } 119 | 120 | /** @see CustomEnchantment#canStack() */ 121 | public void setCanStack(final boolean stacks) { 122 | this.stacks = stacks; 123 | } 124 | 125 | /** 126 | * @return whether or not the enchantment is obtainable without commands 127 | */ 128 | public boolean isEnabled() { 129 | return enabled; 130 | } 131 | 132 | /** 133 | * @return whether or not the enchantment is obtainable 134 | */ 135 | public boolean isTableEnabled() { 136 | return enabled && tableEnabled; 137 | } 138 | 139 | /** @see CustomEnchantment#isTableEnabled() */ 140 | public void setTableEnabled(final boolean tableEnabled) { 141 | this.tableEnabled = tableEnabled; 142 | } 143 | 144 | /** 145 | * @return the max level the enchantment can normally reach via combining enchantments 146 | */ 147 | public int getMaxLevel() { 148 | return maxLevel; 149 | } 150 | 151 | /** 152 | * @return the max level the enchantment can be from an enchanting table 153 | */ 154 | public int getMaxTableLevel() { 155 | return maxTableLevel; 156 | } 157 | 158 | /** 159 | * Sets both the max table level and combine level to the given amount. 160 | * 161 | * @param maxLevel max normally obtainable level 162 | */ 163 | public void setMaxLevel(final int maxLevel) { 164 | setMaxLevel(maxLevel, maxLevel); 165 | if (!setFactors) { 166 | enchantLevelScaleFactor = 60 / maxLevel; 167 | } 168 | } 169 | 170 | /** 171 | * @see CustomEnchantment#getMaxLevel() 172 | * @see CustomEnchantment#getMaxTableLevel() 173 | */ 174 | public void setMaxLevel(final int maxLevel, final int maxTableLevel) { 175 | Validate.isTrue(maxTableLevel > 0, "Max table level must be at least 1"); 176 | Validate.isTrue(maxLevel >= maxTableLevel, "Max level must be at least 1"); 177 | this.maxLevel = maxLevel; 178 | this.maxTableLevel = maxTableLevel; 179 | } 180 | 181 | /** 182 | * @return minimum modified enchantment level needed to receive this enchantment 183 | * @apiNote modified enchantment level is normally between 2 and 48 184 | */ 185 | public double getMinEnchantingLevel() { 186 | return minEnchantingLevel; 187 | } 188 | 189 | public void setMinEnchantingLevel(final double minEnchantingLevel) { 190 | setFactors = true; 191 | this.minEnchantingLevel = minEnchantingLevel; 192 | } 193 | 194 | public double getEnchantLevelScaleFactor() { 195 | return enchantLevelScaleFactor; 196 | } 197 | 198 | public void setEnchantLevelScaleFactor(final double enchantLevelScaleFactor) { 199 | Validate.isTrue(enchantLevelScaleFactor >= 0, "Scale factor must be a non-negative number"); 200 | setFactors = true; 201 | this.enchantLevelScaleFactor = enchantLevelScaleFactor; 202 | } 203 | 204 | public double getEnchantLevelBuffer() { 205 | return enchantLevelBuffer; 206 | } 207 | 208 | public void setEnchantLevelBuffer(final double enchantLevelBuffer) { 209 | Validate.isTrue(enchantLevelBuffer >= 0, "Buffer cannot be negative"); 210 | setFactors = true; 211 | this.enchantLevelBuffer = enchantLevelBuffer; 212 | } 213 | 214 | public int getCombineCostPerLevel() { 215 | return combineCostPerLevel; 216 | } 217 | 218 | public void setCombineCostPerLevel(final int combineCostPerLevel) { 219 | Validate.isTrue(combineCostPerLevel >= 0, "Combine cost cannot be negative"); 220 | this.combineCostPerLevel = combineCostPerLevel; 221 | } 222 | 223 | public Set getNaturalItems() { 224 | return naturalItems; 225 | } 226 | 227 | public void addNaturalItems(final Material... materials) { 228 | for (Material material : materials) { 229 | Objects.requireNonNull(material, "Cannot add a null natural material"); 230 | naturalItems.add(material); 231 | } 232 | } 233 | 234 | public Set getAnvilItems() { 235 | return anvilItems; 236 | } 237 | 238 | public void addAnvilItems(final Material... materials) { 239 | for (Material material : materials) { 240 | Objects.requireNonNull(material, "Cannot add a null natural material"); 241 | anvilItems.add(material); 242 | } 243 | } 244 | 245 | public double getWeight(final Material material) { 246 | return materialWeights.getOrDefault(material, weight); 247 | } 248 | 249 | public void setWeight(final double weight) { 250 | this.weight = weight; 251 | } 252 | 253 | public void setWeight(final Material material, final double weight) { 254 | Validate.isTrue(weight > 0, "Weight must be a positive number"); 255 | this.materialWeights.put(material, weight); 256 | } 257 | 258 | public String getGroup() { 259 | return group; 260 | } 261 | 262 | public void setGroup(final String group) { 263 | Validate.notEmpty(group, "Group cannot be empty or missing"); 264 | this.group = group; 265 | } 266 | 267 | // --- Functional Methods --- // 268 | 269 | /** 270 | * @param expLevel enchanting level (from enchanting table) 271 | * @return enchantment level 272 | */ 273 | public int computeLevel(final int expLevel) { 274 | final int level = Math.min(1 + (int)Math.floor((expLevel - minEnchantingLevel) / enchantLevelScaleFactor), this.maxTableLevel); 275 | final double cap = minEnchantingLevel + level * enchantLevelScaleFactor + enchantLevelBuffer; 276 | return expLevel <= cap ? Math.max(0, level) : 0; 277 | } 278 | 279 | /** 280 | * @param item item to check 281 | * @return true if can go onto the item, not including conflicts with other enchantments 282 | */ 283 | public boolean canEnchantOnto(final ItemStack item) { 284 | if (!isPresent(item)) { 285 | return false; 286 | } 287 | 288 | final Material material = item.getType(); 289 | return material == Material.BOOK || material == Material.ENCHANTED_BOOK || naturalItems.contains(material); 290 | } 291 | 292 | /** 293 | * @param item item to check 294 | * @return true if can merge onto the item, not including conflicts with other enchantments 295 | */ 296 | public boolean canMergeOnto(final ItemStack item) { 297 | if (!isPresent(item)) { 298 | return false; 299 | } 300 | 301 | final Material material = item.getType(); 302 | return material == Material.BOOK || material == Material.ENCHANTED_BOOK 303 | || naturalItems.contains(material) || anvilItems.contains(material); 304 | } 305 | 306 | /** 307 | * Checks whether or not this enchantment works with the given enchantment 308 | * 309 | * @param other enchantment to check against 310 | * @param same whether or not to allow the same enchantment (in case of merging) 311 | * @return true if there is a conflict, false otherwise 312 | */ 313 | public boolean conflictsWith(final CustomEnchantment other, final boolean same) { 314 | Objects.requireNonNull(other, "Cannot check against a null item"); 315 | if (other == this) { 316 | return same; 317 | } 318 | return !group.equals(DEFAULT_GROUP) && group.equals(other.getGroup()); 319 | } 320 | 321 | /** 322 | * Checks whether or not this enchantment works with all of the given enchantments 323 | * 324 | * @param enchantments enchantments to check against 325 | * @param same whether or not to allow the same enchantment (in case of merging) 326 | * @return true if there is a conflict, false otherwise 327 | */ 328 | public boolean conflictsWith(final List enchantments, final boolean same) { 329 | Objects.requireNonNull(enchantments, "Cannot check a null enchantment list"); 330 | return enchantments.stream().anyMatch(enchant -> conflictsWith(this, same)); 331 | } 332 | 333 | /** 334 | * Checks whether or not this enchantment works with all of the given enchantments 335 | * 336 | * @param same whether or not to allow the same enchantment (in case of merging) 337 | * @param enchantments enchantments to check against 338 | * @return true if there is a conflict, false otherwise 339 | */ 340 | public boolean conflictsWith(final boolean same, final CustomEnchantment... enchantments) { 341 | return Arrays.stream(enchantments).anyMatch(enchant -> conflictsWith(this, same)); 342 | } 343 | 344 | /** 345 | * @param item item to add to 346 | * @param level enchantment level 347 | * @return item with the enchantment 348 | */ 349 | public ItemStack addToItem(final ItemStack item, final int level) { 350 | Objects.requireNonNull(item, "Item cannot be null"); 351 | Validate.isTrue(level > 0, "Level must be at least 1"); 352 | 353 | if (item.getType() == Material.BOOK) { 354 | item.setType(Material.ENCHANTED_BOOK); 355 | } 356 | 357 | final ItemMeta meta = item.getItemMeta(); 358 | final List lore = meta.hasLore() ? meta.getLore() : new ArrayList<>(); 359 | 360 | final int lvl = Enchantments.getCustomEnchantments(item).getOrDefault(this, 0); 361 | if (lvl > 0) { 362 | lore.remove(LoreReader.formatEnchantment(this, lvl)); 363 | } 364 | 365 | lore.add(0, LoreReader.formatEnchantment(this, level)); 366 | meta.setLore(lore); 367 | item.setItemMeta(meta); 368 | return item; 369 | } 370 | 371 | /** 372 | * @param item item to remove from 373 | * @return item without this enchantment 374 | */ 375 | public ItemStack removeFromItem(final ItemStack item) { 376 | Objects.requireNonNull(item, "Item cannot be null"); 377 | 378 | final int lvl = Enchantments.getCustomEnchantments(item).getOrDefault(this, 0); 379 | if (lvl > 0) { 380 | final ItemMeta meta = item.getItemMeta(); 381 | final List lore = meta.getLore(); 382 | lore.remove(LoreReader.formatEnchantment(this, lvl)); 383 | meta.setLore(lore); 384 | item.setItemMeta(meta); 385 | } 386 | 387 | return item; 388 | } 389 | 390 | public void addToEnchantment(final Map enchantments, final ItemStack result, final int level) { 391 | addToItem(result, level); 392 | } 393 | 394 | /** 395 | * @param permissible person to receive the enchantment 396 | * @return true if they have permission, false otherwise 397 | */ 398 | public boolean hasPermission(final Permissible permissible) { 399 | return permissible == null 400 | || permissible.hasPermission(Permission.ENCHANT) 401 | || permissible.hasPermission(getPermission()); 402 | } 403 | 404 | /** 405 | * @return permission used by the enchantment 406 | */ 407 | public String getPermission() { 408 | return Permission.ENCHANT + "." + getName().replace(" ", ""); 409 | } 410 | 411 | // ---- API for effects ---- // 412 | 413 | /** 414 | * Applies the enchantment affect when attacking someone 415 | * 416 | * @param user the entity that has the enchantment 417 | * @param target the entity that was struck by the enchantment 418 | * @param level the level of the used enchantment 419 | * @param event the event details 420 | */ 421 | public void applyOnHit(final LivingEntity user, final LivingEntity target, final int level, final EntityDamageByEntityEvent event) { } 422 | 423 | /** 424 | * Applies the enchantment defensively (when taking damage) 425 | * 426 | * @param user the entity hat has the enchantment 427 | * @param target the entity that attacked the enchantment, can be null 428 | * @param level the level of the used enchantment 429 | * @param event the event details (EntityDamageByEntityEvent, EntityDamageByBlockEvent, or just EntityDamageEvent) 430 | */ 431 | public void applyDefense(final LivingEntity user, final LivingEntity target, final int level, final EntityDamageEvent event) { } 432 | 433 | /** 434 | * Applies effects while breaking blocks (for tool effects) 435 | * 436 | * @param user the player with the enchantment 437 | * @param block the block being broken 438 | * @param event the event details (either BlockBreakEvent or BlockDamageEvent) 439 | */ 440 | public void applyBreak(final LivingEntity user, final Block block, final int level, final BlockEvent event) { } 441 | 442 | /** 443 | * Applies effects when the item is equipped 444 | * 445 | * @param user the player that equipped it 446 | * @param level the level of enchantment 447 | */ 448 | public void applyEquip(final LivingEntity user, final int level) { } 449 | 450 | /** 451 | * Applies effects when the item is unequipped 452 | * 453 | * @param user the player that unequipped it 454 | * @param level the level of enchantment 455 | */ 456 | public void applyUnequip(final LivingEntity user, final int level) { } 457 | 458 | /** 459 | * Applies effects when the player left or right clicks (For other kinds of enchantments like spells) 460 | * 461 | * @param user the player with the enchantment 462 | * @param event the event details 463 | */ 464 | public void applyInteractBlock(final Player user, final int level, final PlayerInteractEvent event) { } 465 | 466 | /** 467 | * Applies effects when the player interacts with an entity 468 | * 469 | * @param user player with the enchantment 470 | * @param level enchantment level 471 | * @param event the event details 472 | */ 473 | public void applyInteractEntity(final Player user, final int level, final PlayerInteractEntityEvent event) { } 474 | 475 | /** 476 | * Applies effects when firing a projectile 477 | * 478 | * @param user entity firing the projectile 479 | * @param level enchantment level 480 | * @param event the event details 481 | */ 482 | public void applyProjectile(final LivingEntity user, final int level, final ProjectileLaunchEvent event) { } 483 | 484 | // ---- Object operations ---- // 485 | 486 | @Override 487 | public String toString() { 488 | return name; 489 | } 490 | 491 | @Override 492 | public boolean equals(final Object other) { 493 | return other instanceof CustomEnchantment && ((CustomEnchantment) other).name.equals(name); 494 | } 495 | 496 | @Override 497 | public int hashCode() { 498 | return name.hashCode(); 499 | } 500 | 501 | @Override 502 | public int compareTo(final CustomEnchantment other) { 503 | return name.compareTo(other.name); 504 | } 505 | 506 | // ---- IO ---- / 507 | 508 | private static final String SAVE_FOLDER = "enchant/custom/"; 509 | 510 | private static final String 511 | COMBINE_COST_PER_LEVEL = "combine-cost-per-level", 512 | DESCRIPTION = "description", 513 | ENABLED = "enabled", 514 | ENCHANT_LEVEL_SCALE_FACTOR = "enchant-level-scale-factor", 515 | ENCHANT_LEVEL_BUFFER = "enchant-level-buffer", 516 | GROUP = "group", 517 | MAX_LEVEL = "max-level", 518 | MAX_TABLE_LEVEL = "max-table-level", 519 | MIN_ENCHANTING_LEVEL = "min-enchanting-level", 520 | NAME = "name", 521 | NATURAL_ITEMS = "natural-items", 522 | ANVIL_ITEMS = "anvil-items", 523 | STACKS = "stacks", 524 | TABLE_ENABLED = "table-enabled", 525 | WEIGHT = "weight", 526 | MATERIAL = "material-weights", 527 | EFFECT = "effect"; 528 | 529 | private static final List 530 | BUFFER_COMMENT = ImmutableList.of("", " How many enchantment levels beyond the max can still yield the enchantment"), 531 | COMBINE_COMMENT = ImmutableList.of("", " Level cost per level of the enchantment"), 532 | LEVEL_SCALE_COMMENT = ImmutableList.of("", " Higher numbers result in requiring higher enchanting levels", " to get higher ranks"), 533 | GROUP_COMMENT = ImmutableList.of("", " When something other than default, prevents multiple enchantments", " in the same group being on the same item"), 534 | MAX_COMMENT = ImmutableList.of("", " Max attainable level from anvils"), 535 | MAX_TABLE_COMMENT = ImmutableList.of("", " Max attainable level from enchanting"), 536 | MIN_COMMENT = ImmutableList.of("", " Minimum enchanting level to receive the enchantment.", " Negatives make it easier to get higher ranks"), 537 | ITEM_COMMENT = ImmutableList.of("", " Items that can receive the enchantment from enchanting or anvils"), 538 | ANVIL_COMMENT = ImmutableList.of("", " Additional items that can only receive the enchantment through anvils"), 539 | STACK_COMMENT = ImmutableList.of("", " Whether or not the same enchantment stacks if on multiple items.", " When false, the highest level is applied"), 540 | TABLE_COMMENT = ImmutableList.of("", " Whether or not this enchantment can be achieved from enchanting"), 541 | WEIGHT_COMMENT = ImmutableList.of("", " How common the enchantment is. Higher numbers are more common."), 542 | MATERIAL_COMMENT = ImmutableList.of("", " Weights for specific materials"), 543 | EFFECT_COMMENT = ImmutableList.of("", " Extra settings specific to the enchantment"); 544 | 545 | protected String getSaveFolder() { 546 | return SAVE_FOLDER; 547 | } 548 | 549 | public void save(final EnchantmentAPI plugin) { 550 | final CommentedConfig config = new CommentedConfig(plugin, getSaveFolder() + key); 551 | final DataSection data = config.getConfig(); 552 | data.clear(); 553 | 554 | data.set(NAME, name); 555 | data.set(DESCRIPTION, description); 556 | data.set(ENABLED, enabled); 557 | 558 | data.set(MAX_LEVEL, maxLevel); 559 | data.setComments(MAX_LEVEL, MAX_COMMENT); 560 | 561 | data.set(MAX_TABLE_LEVEL, maxTableLevel); 562 | data.setComments(MAX_TABLE_LEVEL, MAX_TABLE_COMMENT); 563 | 564 | data.set(GROUP, group); 565 | data.setComments(GROUP, GROUP_COMMENT); 566 | 567 | data.set(NATURAL_ITEMS, naturalItems.stream().map(Material::name).collect(Collectors.toList())); 568 | data.setComments(NATURAL_ITEMS, ITEM_COMMENT); 569 | 570 | data.set(ANVIL_ITEMS, anvilItems.stream().map(Material::name).collect(Collectors.toList())); 571 | data.setComments(ANVIL_ITEMS, ANVIL_COMMENT); 572 | 573 | data.set(WEIGHT, weight); 574 | data.setComments(WEIGHT, WEIGHT_COMMENT); 575 | 576 | final DataSection weightData = data.createSection(MATERIAL); 577 | materialWeights.forEach((material, weight) -> weightData.set(material.name(), weight)); 578 | data.setComments(MATERIAL, MATERIAL_COMMENT); 579 | 580 | data.set(MIN_ENCHANTING_LEVEL, minEnchantingLevel); 581 | data.setComments(MIN_ENCHANTING_LEVEL, MIN_COMMENT); 582 | 583 | data.set(ENCHANT_LEVEL_SCALE_FACTOR, enchantLevelScaleFactor); 584 | data.setComments(ENCHANT_LEVEL_SCALE_FACTOR, LEVEL_SCALE_COMMENT); 585 | 586 | data.set(ENCHANT_LEVEL_BUFFER, enchantLevelBuffer); 587 | data.setComments(ENCHANT_LEVEL_BUFFER, BUFFER_COMMENT); 588 | 589 | data.set(COMBINE_COST_PER_LEVEL, combineCostPerLevel); 590 | data.setComments(COMBINE_COST_PER_LEVEL, COMBINE_COMMENT); 591 | 592 | data.set(STACKS, stacks); 593 | data.setComments(STACKS, STACK_COMMENT); 594 | 595 | data.set(TABLE_ENABLED, tableEnabled); 596 | data.setComments(TABLE_ENABLED, TABLE_COMMENT); 597 | 598 | data.set(EFFECT, settings); 599 | data.setComments(EFFECT, EFFECT_COMMENT); 600 | 601 | config.save(); 602 | } 603 | 604 | public void load(final EnchantmentAPI plugin) { 605 | final CommentedConfig config = new CommentedConfig(plugin, getSaveFolder() + key); 606 | if (!config.getConfigFile().exists()) { 607 | return; 608 | } 609 | 610 | final DataSection data = config.getConfig(); 611 | name = TextFormatter.colorString(data.getString(NAME, name)); 612 | description = TextFormatter.colorString(data.getString(DESCRIPTION, description)); 613 | enabled = data.getBoolean(ENABLED, enabled); 614 | setMaxLevel(data.getInt(MAX_LEVEL, maxLevel), data.getInt(MAX_TABLE_LEVEL, maxTableLevel)); 615 | setGroup(data.getString(GROUP, group)); 616 | setMinEnchantingLevel(data.getDouble(MIN_ENCHANTING_LEVEL, minEnchantingLevel)); 617 | setEnchantLevelScaleFactor(data.getDouble(ENCHANT_LEVEL_SCALE_FACTOR, enchantLevelScaleFactor)); 618 | setEnchantLevelBuffer(data.getDouble(ENCHANT_LEVEL_BUFFER, enchantLevelBuffer)); 619 | setCombineCostPerLevel(data.getInt(COMBINE_COST_PER_LEVEL, combineCostPerLevel)); 620 | setCanStack(data.getBoolean(STACKS, stacks)); 621 | setTableEnabled(data.getBoolean(TABLE_ENABLED, tableEnabled)); 622 | setWeight(data.getDouble(WEIGHT, weight)); 623 | 624 | loadItems(naturalItems, data, NATURAL_ITEMS); 625 | loadItems(anvilItems, data, ANVIL_ITEMS); 626 | 627 | if (data.isSection(MATERIAL)) { 628 | materialWeights.clear(); 629 | final DataSection weightData = data.getSection(MATERIAL); 630 | weightData.keys().forEach(key -> { 631 | final Material material = Material.matchMaterial(key); 632 | if (material == null) { 633 | plugin.getLogger().warning(key + " is not a valid material (in material weights for " + name); 634 | } else { 635 | setWeight(material, weightData.getDouble(key)); 636 | } 637 | }); 638 | } 639 | 640 | if (data.isSection(EFFECT)) { 641 | final Settings settings = new Settings(); 642 | final DataSection file = data.getSection(EFFECT); 643 | file.keys().forEach(key -> settings.set(key, file.get(key))); 644 | this.settings.keys().forEach(key -> settings.checkDefault(key, this.settings.get(key))); 645 | this.settings = settings; 646 | } 647 | } 648 | 649 | private void loadItems(final Set destination, final DataSection data, final String key) { 650 | if (!data.has(key)) return; 651 | 652 | destination.clear(); 653 | destination.addAll(data.getList(key).stream() 654 | .map(line -> line.toUpperCase().replace(' ', '_')) 655 | .map(Material::matchMaterial) 656 | .filter(Objects::nonNull) 657 | .collect(Collectors.toList())); 658 | } 659 | } 660 | -------------------------------------------------------------------------------- /src/com/sucy/enchant/api/EnchantPlugin.java: -------------------------------------------------------------------------------- 1 | package com.sucy.enchant.api; 2 | 3 | /** 4 | * EnchantmentAPI © 2017 5 | * com.sucy.enchant.api.EnchantPlugin 6 | * 7 | * Interface to be implemented by plugins adding new enchantments to the server. 8 | * EnchantmentAPI will find any plugins implementing this class and call the 9 | * registration method on enable. 10 | */ 11 | public interface EnchantPlugin { 12 | 13 | /** 14 | * This is where you should register your enchantments. 15 | * 16 | * @param registry registry to register enchantments with 17 | */ 18 | void registerEnchantments(EnchantmentRegistry registry); 19 | } 20 | -------------------------------------------------------------------------------- /src/com/sucy/enchant/api/EnchantmentRegistry.java: -------------------------------------------------------------------------------- 1 | package com.sucy.enchant.api; 2 | 3 | /** 4 | * EnchantmentAPI © 2017 5 | * com.sucy.enchant.api.EnchantmentRegistry 6 | */ 7 | public interface EnchantmentRegistry { 8 | 9 | /** 10 | * Registers enchantments with the API, allowing them to be available 11 | * in the enchanting tables, commands, and anvils. 12 | * 13 | * @param enchantments enchantments to register 14 | */ 15 | void register(CustomEnchantment... enchantments); 16 | } 17 | -------------------------------------------------------------------------------- /src/com/sucy/enchant/api/Enchantments.java: -------------------------------------------------------------------------------- 1 | package com.sucy.enchant.api; 2 | 3 | import com.sucy.enchant.EnchantmentAPI; 4 | import com.sucy.enchant.data.PlayerEquips; 5 | import com.sucy.enchant.util.LoreReader; 6 | import org.bukkit.Bukkit; 7 | import org.bukkit.Material; 8 | import org.bukkit.enchantments.Enchantment; 9 | import org.bukkit.entity.Player; 10 | import org.bukkit.inventory.ItemFlag; 11 | import org.bukkit.inventory.ItemStack; 12 | import org.bukkit.inventory.meta.EnchantmentStorageMeta; 13 | import org.bukkit.inventory.meta.ItemMeta; 14 | 15 | import java.util.HashMap; 16 | import java.util.List; 17 | import java.util.Map; 18 | import java.util.UUID; 19 | import java.util.stream.Collectors; 20 | 21 | import static com.sucy.enchant.util.Utils.isPresent; 22 | 23 | /** 24 | * EnchantmentAPI © 2017 25 | * com.sucy.enchant.api.Enchantments 26 | */ 27 | public class Enchantments { 28 | 29 | private static final Map EQUIPMENT = new HashMap<>(); 30 | 31 | /** 32 | * @param player player to get enchantments for 33 | * @return combined enchantments on all active equipment the player has 34 | */ 35 | public static Map getEnchantments(final Player player) { 36 | return getEquipmentData(player).getEnchantments(); 37 | } 38 | 39 | /** 40 | * @param player player to get equipment data for 41 | * @return the equipment data tracking the player's enchantments 42 | */ 43 | public static PlayerEquips getEquipmentData(final Player player) { 44 | return EQUIPMENT.computeIfAbsent(player.getUniqueId(), uuid -> new PlayerEquips(player)); 45 | } 46 | 47 | /** 48 | * @param player player to clear equipment data for (will refresh next access) 49 | */ 50 | public static void clearEquipmentData(final Player player) { 51 | EQUIPMENT.remove(player.getUniqueId()).clear(player); 52 | } 53 | 54 | /** 55 | * Clears equipment data for all players, forcing all equipment to refresh 56 | */ 57 | public static void clearAllEquipmentData() { 58 | EQUIPMENT.forEach((id, data) -> data.clear(Bukkit.getPlayer(id))); 59 | EQUIPMENT.clear(); 60 | } 61 | 62 | /** 63 | * @param item item to grab the enchantments from 64 | * @return list of custom enchantments (does not include vanilla enchantments) 65 | */ 66 | public static Map getCustomEnchantments(final ItemStack item) { 67 | 68 | final HashMap list = new HashMap(); 69 | if (!isPresent(item)) return list; 70 | 71 | final ItemMeta meta = item.getItemMeta(); 72 | if (meta == null || !meta.hasLore()) return list; 73 | 74 | final List lore = meta.getLore(); 75 | for (final String line : lore) { 76 | final String name = LoreReader.parseEnchantmentName(line); 77 | if (EnchantmentAPI.isRegistered(name)) { 78 | final CustomEnchantment enchant = EnchantmentAPI.getEnchantment(name); 79 | final int level = LoreReader.parseEnchantmentLevel(line); 80 | if (level > 0) { 81 | list.put(enchant, level); 82 | } 83 | } 84 | 85 | // Short-circuit if we aren't finding valid formatted enchantments 86 | // since all enchantments should be added at the top. 87 | else if (name.isEmpty()) { 88 | return list; 89 | } 90 | } 91 | return list; 92 | } 93 | 94 | /** 95 | * Gets all enchantments on an item, including vanilla enchantments. This wraps 96 | * vanilla enchantments in the CustomEnchantment class for more visibility on 97 | * their settings. 98 | * 99 | * @param item item to get enchantments for. 100 | * @return all enchantments on the item and their levels 101 | */ 102 | public static Map getAllEnchantments(final ItemStack item) { 103 | final Map result = getCustomEnchantments(item); 104 | if (item.hasItemMeta()) { 105 | final ItemMeta meta = item.getItemMeta(); 106 | if (!meta.hasItemFlag(ItemFlag.HIDE_ENCHANTS)) { 107 | merge(meta.getEnchants(), result); 108 | if (item.getType() == Material.ENCHANTED_BOOK) { 109 | merge(((EnchantmentStorageMeta) meta).getStoredEnchants(), result); 110 | } 111 | } 112 | } 113 | return result; 114 | } 115 | 116 | /** 117 | * Checks whether or not the item has the enchantment on it 118 | * 119 | * @param item item to check 120 | * @param enchantmentName name of the enchantment to check for 121 | * @return true if it has the enchantment, false otherwise 122 | */ 123 | public static boolean hasCustomEnchantment(final ItemStack item, final String enchantmentName) { 124 | if (!item.hasItemMeta()) return false; 125 | final ItemMeta meta = item.getItemMeta(); 126 | return meta.hasLore() && meta.getLore().stream().anyMatch(LoreReader::isEnchantment); 127 | } 128 | 129 | /** 130 | * Removes all enchantments from the item, including vanilla enchantments 131 | * 132 | * @param item item to remove all enchantments from 133 | * @return item with all enchantments removed 134 | */ 135 | public static ItemStack removeAllEnchantments(final ItemStack item) { 136 | item.getEnchantments().forEach((enchant, level) -> item.removeEnchantment(enchant)); 137 | return removeCustomEnchantments(item); 138 | } 139 | 140 | /** 141 | * Removes all custom enchantments from an item 142 | * 143 | * @param item item to remove enchantments from 144 | * @return item without custom enchantments 145 | */ 146 | public static ItemStack removeCustomEnchantments(final ItemStack item) { 147 | final ItemMeta meta = item.getItemMeta(); 148 | if (meta.hasLore()) { 149 | meta.setLore(meta.getLore().stream() 150 | .filter(line -> !LoreReader.isEnchantment(line)) 151 | .collect(Collectors.toList())); 152 | } 153 | return item; 154 | } 155 | 156 | private static void merge(final Map source, final Map result) { 157 | source.forEach((enchant, level) -> result.put(EnchantmentAPI.getEnchantment(enchant.getName()), level)); 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /src/com/sucy/enchant/api/GlowEffects.java: -------------------------------------------------------------------------------- 1 | package com.sucy.enchant.api; 2 | 3 | import org.bukkit.Material; 4 | import org.bukkit.enchantments.Enchantment; 5 | import org.bukkit.inventory.ItemFlag; 6 | import org.bukkit.inventory.ItemStack; 7 | import org.bukkit.inventory.meta.ItemMeta; 8 | 9 | /** 10 | * EnchantmentAPI © 2017 11 | * com.sucy.enchant.api.GlowEffects 12 | */ 13 | public class GlowEffects { 14 | 15 | /** 16 | * Ensures an item has a glowing effect if it has any enchantments, custom or not. 17 | * 18 | * @param item item to give glowing effects to 19 | * @return item with the glowing effect 20 | */ 21 | public static ItemStack finalize(final ItemStack item) { 22 | // Enchanted books always glow 23 | if (item.getType() == Material.ENCHANTED_BOOK) return item; 24 | 25 | final ItemMeta meta = item.getItemMeta(); 26 | 27 | // Hide enchantments and add a non-impactful enchantment to get the glowing effect 28 | if (item.getEnchantments().isEmpty() && !Enchantments.getCustomEnchantments(item).isEmpty()) { 29 | meta.addEnchant(chooseHidden(item), 1, true); 30 | meta.addItemFlags(ItemFlag.HIDE_ENCHANTS); 31 | } 32 | 33 | // Stop hiding enchantments if a normal vanilla enchantment was added 34 | else if (meta.hasItemFlag(ItemFlag.HIDE_ENCHANTS)) { 35 | final Enchantment hidden = chooseHidden(item); 36 | if (meta.getEnchants().getOrDefault(hidden, 0) == 1 && meta.getEnchants().size() > 1) { 37 | meta.removeEnchant(hidden); 38 | } 39 | if (!meta.getEnchants().containsKey(hidden)) { 40 | meta.removeItemFlags(ItemFlag.HIDE_ENCHANTS); 41 | } 42 | } 43 | 44 | item.setItemMeta(meta); 45 | return item; 46 | } 47 | 48 | private static Enchantment chooseHidden(final ItemStack item) { 49 | if (item.getType() == Material.ENCHANTED_BOOK) { 50 | return Enchantment.BINDING_CURSE; 51 | } else if (item.getType() == Material.BOW) { 52 | return Enchantment.LUCK; 53 | } else { 54 | return Enchantment.ARROW_INFINITE; 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/com/sucy/enchant/api/ItemSet.java: -------------------------------------------------------------------------------- 1 | package com.sucy.enchant.api; 2 | 3 | import org.bukkit.Material; 4 | 5 | import java.util.Arrays; 6 | 7 | /** 8 | * EnchantmentAPI © 2017 9 | * com.sucy.enchant.api.ItemSet 10 | */ 11 | public enum ItemSet { 12 | 13 | BOOK_AND_QUILL("BOOK_AND_QUILL", "WRITTEN_BOOK"), 14 | INK_SACK("INK_SAC", "INK_SACK"), 15 | 16 | AXES("_AXE"), 17 | BOOTS("BOOTS"), 18 | BOWS(Material.BOW), 19 | CHESTPLATES("CHESTPLATE"), 20 | FISHING(Material.FISHING_ROD), 21 | GLIDERS(Material.ELYTRA), 22 | HELMETS("HELMET"), 23 | HOES("_HOE"), 24 | LEGGINGS("LEGGINGS"), 25 | MISCELLANEOUS("SKELETON_SKULL", "SKULL_ITEM", "PUMPKIN"), 26 | PICKAXES("PICKAXE"), 27 | SHEARS(Material.SHEARS), 28 | SHIELDS(Material.SHIELD), 29 | SHOVELS("SHOVEL", "SPADE"), 30 | SWORDS("SWORD"), 31 | TRIDENT("TRIDENT"), 32 | UTILITY("SHEARS", "FLINT_AND_STEEL", "CARROT_STICK", "CARROT_ON_A_STICK"), 33 | 34 | ARMOR(CHESTPLATES, HELMETS, BOOTS, LEGGINGS), 35 | WEAPONS(SWORDS, AXES), 36 | TOOLS(AXES, SHOVELS, PICKAXES), 37 | DURABILITY(SWORDS, TOOLS, BOWS, FISHING, ARMOR), 38 | DURABILITY_SECONDARY(UTILITY, HOES, GLIDERS, SHIELDS), 39 | DURABILITY_ALL(DURABILITY, DURABILITY_SECONDARY), 40 | 41 | VANILLA_ENCHANTABLES(SWORDS, TRIDENT, TOOLS, BOWS, FISHING, ARMOR, UTILITY, GLIDERS, MISCELLANEOUS), 42 | 43 | NONE, 44 | ALL(DURABILITY, DURABILITY_SECONDARY, MISCELLANEOUS); 45 | 46 | private final Material[] items; 47 | 48 | ItemSet() { 49 | items = new Material[0]; 50 | } 51 | 52 | ItemSet(final String... suffixes) { 53 | items = Arrays.stream(Material.values()) 54 | .filter(material -> !material.name().startsWith("LEGACY") 55 | && Arrays.stream(suffixes).anyMatch(material.name()::endsWith)) 56 | .toArray(Material[]::new); 57 | } 58 | 59 | ItemSet(final ItemSet... sets) { 60 | items = Arrays.stream(sets) 61 | .map(ItemSet::getItems) 62 | .flatMap(Arrays::stream) 63 | .toArray(Material[]::new); 64 | } 65 | 66 | ItemSet(final Material... items) { 67 | this.items = items; 68 | } 69 | 70 | public Material[] getItems() { 71 | return items; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/com/sucy/enchant/api/Settings.java: -------------------------------------------------------------------------------- 1 | package com.sucy.enchant.api; 2 | 3 | import com.rit.sucy.config.parse.DataSection; 4 | 5 | /** 6 | * EnchantmentAPI © 2017 7 | * com.sucy.enchant.api.Settings 8 | */ 9 | public class Settings extends DataSection { 10 | 11 | private static final String BASE = "-base"; 12 | private static final String SCALE = "-scale"; 13 | 14 | /** 15 | * Sets a scaling configuration setting 16 | * 17 | * @param key setting key 18 | * @param base base value (at enchantment level 1) 19 | * @param scale value scale (extra per enchantment level) 20 | */ 21 | public void set(final String key, final double base, final double scale) { 22 | set(key + BASE, base); 23 | set(key + SCALE, scale); 24 | } 25 | 26 | /** 27 | * Gets a scaling setting based on the provided level 28 | * 29 | * @param key setting key 30 | * @param level enchantment level 31 | * @return scaled setting value 32 | */ 33 | public double get(final String key, final int level) { 34 | return getDouble(key + BASE, 0) + getDouble(key + SCALE, 0) * (level - 1); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/com/sucy/enchant/api/Tasks.java: -------------------------------------------------------------------------------- 1 | package com.sucy.enchant.api; 2 | 3 | import com.sucy.enchant.EnchantmentAPI; 4 | import org.bukkit.scheduler.BukkitRunnable; 5 | import org.bukkit.scheduler.BukkitTask; 6 | 7 | import java.util.Objects; 8 | 9 | /** 10 | * EnchantmentAPI © 2017 11 | * com.sucy.enchant.api.Tasks 12 | * 13 | * Utility class providing ways to register tasks through EnchantmentAPI 14 | */ 15 | public class Tasks { 16 | 17 | private static EnchantmentAPI plugin = EnchantmentAPI.getPlugin(EnchantmentAPI.class); 18 | 19 | /** 20 | * Schedules a task to run next tick 21 | * 22 | * @param runnable runnable to execute 23 | * @return task handling the runnable 24 | */ 25 | public static BukkitTask schedule(final Runnable runnable) { 26 | return schedule(runnable, 1); 27 | } 28 | 29 | /** 30 | * Schedules a task to run after the specified number of ticks 31 | * 32 | * @param runnable runnable to execute 33 | * @param delay number of ticks to wait 34 | * @return task handling the runnable 35 | */ 36 | public static BukkitTask schedule(final Runnable runnable, final int delay) { 37 | Objects.requireNonNull(runnable, "Runnable cannot be null"); 38 | 39 | return plugin.getServer().getScheduler().runTaskLater(plugin, runnable, delay); 40 | } 41 | 42 | /** 43 | * Schedules a task to run continuously 44 | * 45 | * @param runnable runnable to execute 46 | * @param delay delay in ticks before running the task the first time 47 | * @param interval time in ticks between each subsequent execution 48 | * @return task handling the runnable 49 | */ 50 | public static BukkitTask schedule(final Runnable runnable, final int delay, final int interval) { 51 | Objects.requireNonNull(runnable, "Runnable cannot be null"); 52 | 53 | return plugin.getServer().getScheduler().runTaskTimer(plugin, runnable, delay, interval); 54 | } 55 | 56 | /** 57 | * Schedules a task to run a given number of times 58 | * 59 | * @param runnable runnable to execute 60 | * @param delay delay in ticks before running the task the first time 61 | * @param interval time in ticks between each subsequent execution 62 | * @param repetitions number of times the task should run 63 | * @return task handling the runnable 64 | */ 65 | public static BukkitTask schedule(final Runnable runnable, final int delay, final int interval, int repetitions) { 66 | Objects.requireNonNull(runnable, "Runnable cannot be null"); 67 | 68 | return new RepeatTask(runnable, repetitions).runTaskTimer(plugin, delay, interval); 69 | } 70 | 71 | private static class RepeatTask extends BukkitRunnable { 72 | private final Runnable runnable; 73 | private int repetitions; 74 | public RepeatTask(final Runnable runnable, final int repetitions) { 75 | this.runnable = runnable; 76 | this.repetitions = repetitions; 77 | } 78 | @Override 79 | public void run() { 80 | runnable.run(); 81 | repetitions--; 82 | if (repetitions <= 0) { 83 | cancel(); 84 | } 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/com/sucy/enchant/cmd/CmdAdd.java: -------------------------------------------------------------------------------- 1 | package com.sucy.enchant.cmd; 2 | 3 | import com.rit.sucy.commands.CommandManager; 4 | import com.rit.sucy.commands.ConfigurableCommand; 5 | import com.rit.sucy.commands.IFunction; 6 | import com.sucy.enchant.EnchantmentAPI; 7 | import com.sucy.enchant.api.CustomEnchantment; 8 | import com.sucy.enchant.api.Enchantments; 9 | import com.sucy.enchant.api.GlowEffects; 10 | import com.sucy.enchant.api.Tasks; 11 | import com.sucy.enchant.data.PlayerEquips; 12 | import org.bukkit.ChatColor; 13 | import org.bukkit.command.CommandSender; 14 | import org.bukkit.entity.Player; 15 | import org.bukkit.plugin.Plugin; 16 | 17 | import static com.sucy.enchant.util.Utils.isPresent; 18 | 19 | /** 20 | * EnchantmentAPI © 2017 21 | * com.sucy.enchant.cmd.CmdAdd 22 | */ 23 | public class CmdAdd implements IFunction { 24 | 25 | private static final String PLAYER_ONLY = "player-only"; 26 | private static final String NOT_ENCHANTMENT = "not-enchantment"; 27 | private static final String NOT_LEVEL = "not-level"; 28 | private static final String NO_ITEM = "no-item"; 29 | private static final String SUCCESS = "success"; 30 | 31 | @Override 32 | public void execute( 33 | final ConfigurableCommand command, 34 | final Plugin plugin, 35 | final CommandSender sender, 36 | final String[] args) { 37 | 38 | if (!(sender instanceof Player)) { 39 | command.sendMessage(sender, PLAYER_ONLY, ChatColor.DARK_RED + "Only players can use this command"); 40 | return; 41 | } 42 | 43 | if (args.length < 2) { 44 | CommandManager.displayUsage(command, sender); 45 | return; 46 | } 47 | 48 | final Player player = (Player)sender; 49 | if (!isPresent(player.getEquipment().getItemInMainHand())) { 50 | command.sendMessage(sender, NO_ITEM, ChatColor.DARK_RED + "You are not holding an item to enchant"); 51 | } 52 | 53 | final StringBuilder builder = new StringBuilder(args[0]); 54 | for (int i = 1; i < args.length - 1; i++) { 55 | builder.append(' '); 56 | builder.append(args[i]); 57 | } 58 | 59 | final CustomEnchantment enchantment = EnchantmentAPI.getEnchantment(builder.toString()); 60 | if (enchantment == null) { 61 | command.sendMessage(sender, NOT_ENCHANTMENT, ChatColor.GOLD + builder.toString() + ChatColor.DARK_RED + " is not an enchantment"); 62 | return; 63 | } 64 | 65 | final int level; 66 | try { 67 | level = Integer.parseInt(args[args.length - 1]); 68 | } catch (final Exception ex) { 69 | command.sendMessage(sender, NOT_LEVEL, ChatColor.GOLD + args[args.length - 1] + ChatColor.DARK_RED + " is not a number"); 70 | return; 71 | } 72 | 73 | enchantment.addToItem(player.getInventory().getItemInMainHand(), level); 74 | GlowEffects.finalize(player.getInventory().getItemInMainHand()); 75 | final PlayerEquips equips = Enchantments.getEquipmentData(player); 76 | Tasks.schedule(() -> equips.updateWeapon((player).getInventory())); 77 | command.sendMessage(sender, SUCCESS, ChatColor.DARK_GREEN + "Added the enchantment successfully"); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/com/sucy/enchant/cmd/CmdBook.java: -------------------------------------------------------------------------------- 1 | package com.sucy.enchant.cmd; 2 | 3 | import com.rit.sucy.commands.ConfigurableCommand; 4 | import com.rit.sucy.commands.IFunction; 5 | import com.rit.sucy.items.ItemManager; 6 | import com.sucy.enchant.EnchantmentAPI; 7 | import com.sucy.enchant.api.CustomEnchantment; 8 | import com.sucy.enchant.api.ItemSet; 9 | import com.sucy.enchant.vanilla.VanillaEnchantment; 10 | import org.bukkit.Material; 11 | import org.bukkit.command.CommandSender; 12 | import org.bukkit.entity.Player; 13 | import org.bukkit.inventory.ItemStack; 14 | import org.bukkit.inventory.meta.BookMeta; 15 | import org.bukkit.plugin.Plugin; 16 | 17 | import java.util.ArrayList; 18 | import java.util.Collections; 19 | 20 | /** 21 | * EnchantmentAPI © 2017 22 | * com.sucy.enchant.cmd.CmdBook 23 | */ 24 | public class CmdBook implements IFunction { 25 | 26 | private static final String NOT_PLAYER = "not-player"; 27 | private static final String SUCCESS = "success"; 28 | 29 | @Override 30 | public void execute( 31 | final ConfigurableCommand command, 32 | final Plugin plugin, 33 | final CommandSender sender, 34 | final String[] args) { 35 | 36 | if (!(sender instanceof Player)) { 37 | command.sendMessage(sender, NOT_PLAYER, "&4You must be a player to use this command"); 38 | return; 39 | } 40 | 41 | final ItemStack book = new ItemStack(ItemSet.BOOK_AND_QUILL.getItems()[0]); 42 | final BookMeta meta = (BookMeta)book.getItemMeta(); 43 | meta.addPage("EnchantmentAPI\nBy Eniripsa96\n\n Enchantment details"); 44 | meta.setAuthor("Eniripsa96"); 45 | meta.setTitle("EnchantmentAPI"); 46 | 47 | final ArrayList enchants = new ArrayList<>(EnchantmentAPI.getRegisteredEnchantments()); 48 | Collections.sort(enchants); 49 | 50 | for (final CustomEnchantment enchantment : enchants) { 51 | if (enchantment instanceof VanillaEnchantment) continue; 52 | if (enchantment.getDescription() == null) continue; 53 | StringBuilder page = new StringBuilder(); 54 | page.append(enchantment.getName()).append(" - ").append(enchantment.getDescription()).append("\n\nItems: "); 55 | 56 | boolean first = true; 57 | for (Material item : enchantment.getNaturalItems()) { 58 | if (first) first = false; 59 | else page.append(", "); 60 | page.append(ItemManager.getVanillaName(item)); 61 | } 62 | if (first) page.append("None"); 63 | meta.addPage(page.toString()); 64 | } 65 | book.setItemMeta(meta); 66 | ((Player)sender).getInventory().addItem(book); 67 | command.sendMessage(sender, SUCCESS, "&2You have received a book with all enchantment details"); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/com/sucy/enchant/cmd/CmdGraph.java: -------------------------------------------------------------------------------- 1 | package com.sucy.enchant.cmd; 2 | 3 | import com.rit.sucy.commands.ConfigurableCommand; 4 | import com.rit.sucy.commands.IFunction; 5 | import com.sucy.enchant.EnchantmentAPI; 6 | import com.sucy.enchant.api.CustomEnchantment; 7 | import com.sucy.enchant.data.ConfigKey; 8 | import com.sucy.enchant.data.Configuration; 9 | import com.sucy.enchant.data.Enchantability; 10 | import com.sucy.enchant.mechanics.EnchantingMechanics; 11 | import com.sucy.enchant.vanilla.Vanilla; 12 | import org.bukkit.ChatColor; 13 | import org.bukkit.Material; 14 | import org.bukkit.command.CommandSender; 15 | import org.bukkit.entity.Player; 16 | import org.bukkit.inventory.ItemStack; 17 | import org.bukkit.plugin.Plugin; 18 | 19 | import java.text.DecimalFormat; 20 | import java.util.ArrayList; 21 | import java.util.Comparator; 22 | import java.util.HashSet; 23 | import java.util.List; 24 | import java.util.Map; 25 | import java.util.Set; 26 | import java.util.stream.Collectors; 27 | 28 | /** 29 | * EnchantmentAPI © 2017 30 | * com.sucy.enchant.cmd.CmdGraph 31 | */ 32 | public class CmdGraph implements IFunction { 33 | 34 | private static final String INVALID_MATERIAL = "invalid-material"; 35 | private static final String INVALID_ENCHANTMENT = "invalid-enchantment"; 36 | private static final String INCOMPATIBLE_ENCHANTMENT = "incompatible-enchantment"; 37 | 38 | private static final DecimalFormat FORMAT = new DecimalFormat("##0.0"); 39 | 40 | private final EnchantingMechanics MECHANICS = new EnchantingMechanics(); 41 | 42 | private final int maxEnchantsSetting = Configuration.amount(ConfigKey.MAX_ENCHANTMENTS); 43 | 44 | @Override 45 | public void execute( 46 | final ConfigurableCommand command, 47 | final Plugin plugin, 48 | final CommandSender sender, 49 | final String[] args) { 50 | 51 | if (args.length >= 2) { 52 | try { 53 | 54 | // Parse the item 55 | final Material mat = Material.getMaterial(args[0].toUpperCase()); 56 | if (mat == null) { 57 | command.sendMessage(sender, INVALID_MATERIAL, "That is not a valid material"); 58 | return; 59 | } 60 | 61 | final ItemStack item = new ItemStack(mat); 62 | 63 | // Make sure the enchantment can work on the item 64 | String name = args[1]; 65 | for (int i = 2; i < args.length; i++) name += " " + args[i]; 66 | final CustomEnchantment enchant = Vanilla.getEnchantment(name).orElse(EnchantmentAPI.getEnchantment(name)); 67 | if (enchant == null) { 68 | command.sendMessage(sender, INVALID_ENCHANTMENT, "&4That is not a valid enchantment"); 69 | return; 70 | } 71 | else if (!enchant.canEnchantOnto(item)) { 72 | command.sendMessage(sender, INCOMPATIBLE_ENCHANTMENT, "&4That enchantment doesn't work on that item"); 73 | return; 74 | } 75 | 76 | // Run the computation task (usually takes 5-10ms) 77 | plugin.getServer() 78 | .getScheduler() 79 | .runTaskAsynchronously(plugin, () -> compute(enchant, sender, item)); 80 | } 81 | catch (final Exception e) { 82 | // Do nothing 83 | } 84 | } 85 | } 86 | 87 | private int next; 88 | private int getNext() { 89 | return next++; 90 | } 91 | 92 | private double total(final Map> grouped, final String key, final Material material) { 93 | return grouped.get(key).stream().mapToDouble(e -> e.getWeight(material)).sum(); 94 | } 95 | 96 | void compute(final CustomEnchantment enchant, final CommandSender sender, final ItemStack item) { 97 | final Player enchanter = (sender instanceof Player) ? (Player)sender : null; 98 | final int enchantability = Enchantability.determine(item.getType()); 99 | 100 | // Part 1 - Selecting n weighted random enchantments 101 | final int maxModified = MECHANICS.maxLevel(30, enchantability); 102 | final List enchantWeights = new ArrayList<>(); 103 | for (int i = 2; i <= maxModified; i++) { 104 | 105 | final List enchants = MECHANICS.getAllValidEnchants(item, enchanter, i, true); 106 | next = 0; 107 | final Map> grouped = enchants.stream().collect( 108 | Collectors.groupingBy(e -> e.getGroup().equals(CustomEnchantment.DEFAULT_GROUP) ? "d" + getNext() : e.getGroup())); 109 | final List groups = grouped.keySet().stream() 110 | .filter(key -> !grouped.get(key).contains(enchant)) 111 | .collect(Collectors.toList()); 112 | final List groupWeights = groups.stream() 113 | .map(key -> total(grouped, key, item.getType())) 114 | .collect(Collectors.toList()); 115 | 116 | final double totalWeight = enchants.stream().mapToDouble(e -> e.getWeight(item.getType())).sum(); 117 | final double targetGroupWeight = enchant.getGroup().equals(CustomEnchantment.DEFAULT_GROUP) 118 | ? enchant.getWeight(item.getType()) 119 | : total(grouped, enchant.getGroup(), item.getType()); 120 | final double enchantGroupWeight = enchant.getWeight(item.getType()) / targetGroupWeight; 121 | 122 | // Number of enchantment odds 123 | final int maxEnchants = Math.min(maxEnchantsSetting, grouped.size()); 124 | final double[] numWeights = new double[maxEnchants]; 125 | double remaining = 1.0; 126 | int lvl = i; 127 | for (int numEnchants = 0; numEnchants < maxEnchants; numEnchants++) { 128 | final double chance = 1 - (lvl + 1) * 0.02; 129 | numWeights[numEnchants] = remaining * (numEnchants < maxEnchants - 1 ? chance : 1); 130 | remaining *= 1 - chance; 131 | lvl /= 2; 132 | } 133 | 134 | // Selecting the enchantment odds 135 | double current = targetGroupWeight / totalWeight; 136 | double total = current * numWeights[0]; 137 | final Set set = new HashSet<>(); 138 | for (int numEnchants = 1; numEnchants < maxEnchants; numEnchants++) { 139 | current += compute(groupWeights, totalWeight, targetGroupWeight, numEnchants, set); 140 | total += current * numWeights[numEnchants]; 141 | } 142 | enchantWeights.add(total * enchantGroupWeight); 143 | } 144 | 145 | // Part 2 - Enchantability distribution 146 | final Map enchantabilitySpread = MECHANICS.enchantabilitySpread(enchantability); 147 | final int enchantabilityWeight = MECHANICS.enchantabilityWeight(enchantability); 148 | final List modifierWeights = enchantabilitySpread.entrySet().stream() 149 | .sorted(Comparator.comparingInt(Map.Entry::getKey)) 150 | .map(e -> (double)e.getValue() / enchantabilityWeight) 151 | .collect(Collectors.toList()); 152 | 153 | // Part 3 - Uniform triangular distributed multiplier 154 | final List result = new ArrayList<>(); 155 | for (int i = 1; i <= 30; i++) { 156 | final double[] weights = new double[enchant.getMaxTableLevel()]; 157 | for (int k = 0; k < modifierWeights.size(); k++) { 158 | final double joined = enchantWeights.get(i + k - 1) * modifierWeights.get(k); 159 | for (int j = 1; j < weights.length; j++) { 160 | weights[j - 1] += MECHANICS.chance(i + 1 + k, enchant, j) * joined; 161 | } 162 | } 163 | result.add(weights); 164 | } 165 | 166 | // Results - render graph 167 | render(sender, enchant, result); 168 | } 169 | 170 | double compute(final List weights, final double totalWeight, final double targetWeight, final int enchants, final Set indices) { 171 | if (enchants == 0) return targetWeight / totalWeight; 172 | 173 | double total = 0; 174 | for (int i = 0; i < weights.size(); i++) { 175 | if (!indices.contains(i)) { 176 | indices.add(i); 177 | total += weights.get(i) * compute(weights, totalWeight - weights.get(i), targetWeight, enchants - 1, indices) / totalWeight; 178 | indices.remove(i); 179 | } 180 | } 181 | return total; 182 | } 183 | 184 | void render(final CommandSender sender, final CustomEnchantment enchant, final List probabilities) { 185 | 186 | // Get the maximum probability 187 | double max = 0; 188 | for (final double[] data : probabilities) { 189 | for (final double prob : data) { 190 | max = Math.max(prob, max); 191 | } 192 | } 193 | max = Math.ceil(max * 1000) / 1000; 194 | 195 | // Construct the graph 196 | for (int i = 8; i >= 0; i--) { 197 | 198 | final ChatColor lc = i == 0 ? ChatColor.GRAY : ChatColor.DARK_GRAY; 199 | 200 | // Label the intervals 201 | final StringBuilder line = new StringBuilder(99) 202 | .append(ChatColor.GOLD).append(FORMAT.format(i * max * 100 / 9)) 203 | .append("-").append(FORMAT.format((i + 1) * max * 100 / 9)) 204 | .append("%").append(lc).append("_"); 205 | while (line.length() < 16) line.append("_"); 206 | line.append(ChatColor.GRAY).append("|"); 207 | 208 | // Print out the points 209 | for (final double[] data : probabilities) { 210 | 211 | // Search if any of the levels fell into the interval for the given level 212 | String piece = lc + "_"; 213 | for (int k = 0; k < data.length; k++) { 214 | if (data[k] > i * max / 9 && data[k] <= (i + 1) * max / 9) { 215 | piece = ChatColor.getByChar((char)(49 + k % 6)) + "X"; 216 | } 217 | } 218 | line.append(piece); 219 | } 220 | 221 | // Send the line of the graph to the sender 222 | sender.sendMessage(line.toString()); 223 | } 224 | 225 | // Output the level scale at the bottom 226 | sender.sendMessage(ChatColor.DARK_GRAY + "||__________" + ChatColor.GRAY + "|" + ChatColor.DARK_GRAY 227 | + "____" + ChatColor.GRAY + "5" + ChatColor.DARK_GRAY + "____" + ChatColor.GRAY + "10" 228 | + ChatColor.DARK_GRAY + "___" + ChatColor.GRAY + "15" + ChatColor.DARK_GRAY + "___" 229 | + ChatColor.GRAY + "20" + ChatColor.DARK_GRAY + "___" + ChatColor.GRAY + "25" + ChatColor.DARK_GRAY 230 | + "___" + ChatColor.GRAY + "30"); 231 | } 232 | } 233 | -------------------------------------------------------------------------------- /src/com/sucy/enchant/cmd/CmdReload.java: -------------------------------------------------------------------------------- 1 | package com.sucy.enchant.cmd; 2 | 3 | import com.rit.sucy.commands.ConfigurableCommand; 4 | import com.rit.sucy.commands.IFunction; 5 | import com.sucy.enchant.EnchantmentAPI; 6 | import org.bukkit.command.CommandSender; 7 | import org.bukkit.plugin.Plugin; 8 | import org.bukkit.plugin.java.JavaPlugin; 9 | 10 | /** 11 | * EnchantmentAPI © 2017 12 | * com.sucy.enchant.cmd.CmdReload 13 | */ 14 | public class CmdReload implements IFunction { 15 | 16 | @Override 17 | public void execute( 18 | final ConfigurableCommand configurableCommand, 19 | final Plugin plugin, 20 | final CommandSender commandSender, 21 | final String[] strings) { 22 | 23 | final EnchantmentAPI enchantmentAPI = JavaPlugin.getPlugin(EnchantmentAPI.class); 24 | enchantmentAPI.onDisable(); 25 | enchantmentAPI.onEnable(); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/com/sucy/enchant/cmd/CmdRemove.java: -------------------------------------------------------------------------------- 1 | package com.sucy.enchant.cmd; 2 | 3 | import com.rit.sucy.commands.ConfigurableCommand; 4 | import com.rit.sucy.commands.IFunction; 5 | import com.sucy.enchant.api.CustomEnchantment; 6 | import com.sucy.enchant.api.Enchantments; 7 | import org.bukkit.command.CommandSender; 8 | import org.bukkit.entity.Player; 9 | import org.bukkit.inventory.ItemStack; 10 | import org.bukkit.plugin.Plugin; 11 | 12 | import java.util.Map; 13 | 14 | import static com.sucy.enchant.util.Utils.isPresent; 15 | 16 | /** 17 | * EnchantmentAPI © 2017 18 | * com.sucy.enchant.cmd.CmdRemove 19 | */ 20 | public class CmdRemove implements IFunction { 21 | 22 | private static final String NOT_PLAYER = "not-player"; 23 | private static final String NO_ITEM = "no-item"; 24 | private static final String NO_ENCHANTS = "no-enchants"; 25 | private static final String REMOVED = "removed"; 26 | 27 | @Override 28 | public void execute( 29 | final ConfigurableCommand command, 30 | final Plugin plugin, 31 | final CommandSender sender, 32 | final String[] strings) { 33 | 34 | if (!(sender instanceof Player)) { 35 | command.sendMessage(sender, NOT_PLAYER, "&4Only players can use this command"); 36 | return; 37 | } 38 | 39 | final Player player = (Player)sender; 40 | final ItemStack item = player.getEquipment().getItemInMainHand(); 41 | if (!isPresent(item)) { 42 | command.sendMessage(sender, NO_ITEM, "&4You don't have an item in your hand"); 43 | return; 44 | } 45 | 46 | final Map enchantments = Enchantments.getAllEnchantments(item); 47 | if (enchantments.isEmpty()) { 48 | command.sendMessage(sender, NO_ENCHANTS, "&4That item doesn't have any enchantments"); 49 | return; 50 | } 51 | 52 | Enchantments.removeAllEnchantments(item); 53 | command.sendMessage(sender, REMOVED, "&2 Removed all enchantments from your held item"); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/com/sucy/enchant/cmd/Commands.java: -------------------------------------------------------------------------------- 1 | package com.sucy.enchant.cmd; 2 | 3 | import com.rit.sucy.commands.CommandManager; 4 | import com.rit.sucy.commands.ConfigurableCommand; 5 | import com.rit.sucy.commands.SenderType; 6 | import com.sucy.enchant.EnchantmentAPI; 7 | 8 | /** 9 | * EnchantmentAPI © 2017 10 | * com.sucy.enchant.cmd.Commands 11 | */ 12 | public class Commands { 13 | 14 | public static void init(final EnchantmentAPI plugin) { 15 | final ConfigurableCommand root = new ConfigurableCommand(plugin, "enchants", SenderType.ANYONE); 16 | root.addSubCommands( 17 | new ConfigurableCommand( 18 | plugin, 19 | "add", 20 | SenderType.PLAYER_ONLY, 21 | new CmdAdd(), 22 | "enchants held item", 23 | " ", 24 | "EnchantmentAPI.admin"), 25 | new ConfigurableCommand( 26 | plugin, 27 | "remove", 28 | SenderType.PLAYER_ONLY, 29 | new CmdRemove(), 30 | "removes enchants", 31 | "", 32 | "EnchantmentAPI.admin"), 33 | new ConfigurableCommand( 34 | plugin, 35 | "reload", 36 | SenderType.ANYONE, 37 | new CmdReload(), 38 | "reloads the plugin", 39 | "", 40 | "EnchantmentAPI.admin"), 41 | new ConfigurableCommand( 42 | plugin, 43 | "graph", 44 | SenderType.ANYONE, 45 | new CmdGraph(), 46 | "graphs probabilities", 47 | " ", 48 | "EnchantmentAPI.admin"), 49 | new ConfigurableCommand( 50 | plugin, 51 | "book", 52 | SenderType.PLAYER_ONLY, 53 | new CmdBook(), 54 | "detailed book", 55 | "", 56 | "EnchantmentAPI.admin") 57 | ); 58 | CommandManager.registerCommand(root); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/com/sucy/enchant/data/ConfigKey.java: -------------------------------------------------------------------------------- 1 | package com.sucy.enchant.data; 2 | 3 | /** 4 | * EnchantmentAPI © 2017 5 | * com.sucy.enchant.data.ConfigKey 6 | */ 7 | public enum ConfigKey { 8 | COLORED_NAMES_IN_ANVILS, 9 | CUSTOM_FISHING, 10 | FISHING_ENCHANTING_LEVEL, 11 | GLOBAL_ANVIL_LEVEL, 12 | MAX_ENCHANTMENTS, 13 | MAX_MERGED_ENCHANTMENTS, 14 | NON_ENCHANTABLES; 15 | 16 | private final String key = name().toLowerCase().replace('_', '-'); 17 | 18 | public String getKey() { 19 | return key; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/com/sucy/enchant/data/Configuration.java: -------------------------------------------------------------------------------- 1 | package com.sucy.enchant.data; 2 | 3 | import com.rit.sucy.config.CommentedConfig; 4 | import com.rit.sucy.config.parse.DataSection; 5 | import com.sucy.enchant.EnchantmentAPI; 6 | 7 | /** 8 | * EnchantmentAPI © 2017 9 | * com.sucy.enchant.data.Configuration 10 | */ 11 | public class Configuration { 12 | 13 | private static DataSection data; 14 | 15 | public static void reload(final EnchantmentAPI plugin) { 16 | final CommentedConfig config = new CommentedConfig(plugin, "config"); 17 | config.saveDefaultConfig(); 18 | config.checkDefaults(); 19 | config.trim(); 20 | config.save(); 21 | data = config.getConfig(); 22 | } 23 | 24 | public static boolean using(final ConfigKey configKey) { 25 | return data.getBoolean(configKey.getKey()); 26 | } 27 | 28 | public static int amount(final ConfigKey configKey) { 29 | return data.getInt(configKey.getKey()); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/com/sucy/enchant/data/Enchantability.java: -------------------------------------------------------------------------------- 1 | package com.sucy.enchant.data; 2 | 3 | import com.google.common.collect.ImmutableList; 4 | import com.rit.sucy.config.CommentedConfig; 5 | import com.rit.sucy.config.parse.DataSection; 6 | import com.rit.sucy.version.VersionManager; 7 | import com.sucy.enchant.EnchantmentAPI; 8 | import org.bukkit.Material; 9 | 10 | import java.util.HashMap; 11 | import java.util.List; 12 | import java.util.Map; 13 | import java.util.stream.Collectors; 14 | 15 | /** 16 | * EnchantmentAPI © 2017 17 | * com.sucy.enchant.data.Enchantability 18 | */ 19 | public class Enchantability { 20 | 21 | private static final String TYPES = "types"; 22 | private static final String ENCHANTABILITY = "enchantability"; 23 | private static final String DEFAULT = "default"; 24 | 25 | private static final Map VALUES = new HashMap<>(); 26 | 27 | private static int defaultValue = 10; 28 | 29 | public static int determine(final Material material) { 30 | return VALUES.getOrDefault(material, defaultValue); 31 | } 32 | 33 | public static void init(final EnchantmentAPI enchantmentAPI) { 34 | final CommentedConfig config = new CommentedConfig(enchantmentAPI, "enchantability"); 35 | checkDefaults(config); 36 | 37 | final DataSection data = config.getConfig(); 38 | defaultValue = data.getInt(DEFAULT, 10); 39 | for (final String key : data.keys()) { 40 | if (data.isSection(key)) { 41 | final DataSection section = data.getSection(key); 42 | if (!section.has(TYPES) || !section.has(ENCHANTABILITY)) { 43 | enchantmentAPI.getLogger().warning(key + " in enchantability.yml is missing required values"); 44 | } else { 45 | final int value = section.getInt(ENCHANTABILITY, 0); 46 | section.getList(TYPES).forEach(type -> { 47 | final Material mat = Material.matchMaterial(type); 48 | if (mat == null) { 49 | enchantmentAPI.getLogger().warning(type + " is not a valid material (under " + key + " in enchantability.yml)"); 50 | } else { 51 | VALUES.put(mat, value); 52 | } 53 | }); 54 | } 55 | } else if (!key.equals(DEFAULT)) { 56 | enchantmentAPI.getLogger().warning(key + " in enchantability.yml is not formatted properly"); 57 | } 58 | } 59 | } 60 | 61 | private static void checkDefaults(final CommentedConfig config) { 62 | final DataSection data = config.getConfig(); 63 | if (!config.getConfigFile().exists()) { 64 | for (final MaterialClass materialClass : MaterialClass.values()) { 65 | populate(data, ARMOR, materialClass.name, "armor", materialClass.armor); 66 | populate(data, WEAPON, materialClass.name, "tool", materialClass.weapon); 67 | } 68 | } 69 | if (!data.has(DEFAULT)) { 70 | data.set(DEFAULT, 1); 71 | config.save(); 72 | } 73 | } 74 | 75 | private static void populate( 76 | final DataSection data, 77 | final List names, 78 | final String material, 79 | final String category, 80 | final int value) { 81 | 82 | if (value == 0) { 83 | return; 84 | } 85 | 86 | final List types = names.stream().map(type -> material + "_" + type).collect(Collectors.toList()); 87 | final DataSection section = data.createSection(material.toLowerCase() + "-" + category); 88 | section.set(TYPES, types); 89 | section.set(ENCHANTABILITY, value); 90 | } 91 | 92 | private static final List ARMOR = ImmutableList.builder() 93 | .add("BOOTS") 94 | .add("CHESTPLATE") 95 | .add("HELMET") 96 | .add("LEGGINGS") 97 | .build(); 98 | 99 | private static final List WEAPON = ImmutableList.builder() 100 | .add("AXE") 101 | .add("HOE") 102 | .add("PICKAXE") 103 | .add(VersionManager.isVersionAtLeast(11300) ? "SHOVEL" : "SPADE") 104 | .add("SWORD") 105 | .build(); 106 | 107 | private enum MaterialClass { 108 | WOOD(0, 15, VersionManager.isVersionAtLeast(11300) ? "WOODEN" : "WOOD"), 109 | STONE(0, 5), 110 | IRON(9, 14), 111 | GOLD(25, 22, VersionManager.isVersionAtLeast(11300) ? "GOLDEN" : "GOLD"), 112 | DIAMOND(10, 10), 113 | LEATHER(15, 0), 114 | CHAINMAIL(12, 0); 115 | 116 | private final int armor; 117 | private final int weapon; 118 | private final String name; 119 | 120 | MaterialClass(final int armor, final int weapon) { 121 | this.armor = armor; 122 | this.weapon = weapon; 123 | this.name = this.name(); 124 | } 125 | 126 | MaterialClass(final int armor, final int weapon, final String name) { 127 | this.armor = armor; 128 | this.weapon = weapon; 129 | this.name = name; 130 | } 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /src/com/sucy/enchant/data/Path.java: -------------------------------------------------------------------------------- 1 | package com.sucy.enchant.data; 2 | 3 | /** 4 | * EnchantmentAPI © 2017 5 | * com.sucy.enchant.data.Path 6 | */ 7 | public class Path { 8 | public static final String DATA_FOLDER = "data/"; 9 | } 10 | -------------------------------------------------------------------------------- /src/com/sucy/enchant/data/Permission.java: -------------------------------------------------------------------------------- 1 | package com.sucy.enchant.data; 2 | 3 | /** 4 | * EnchantmentAPI © 2017 5 | * com.sucy.enchant.data.Permission 6 | */ 7 | public class Permission { 8 | private static final String PREFIX = "EnchantmentAPI."; 9 | 10 | public static final String ENCHANT = PREFIX + "enchant"; 11 | public static final String ENCHANT_VANILLA = ENCHANT + ".vanilla"; 12 | 13 | } 14 | -------------------------------------------------------------------------------- /src/com/sucy/enchant/data/PlayerEquips.java: -------------------------------------------------------------------------------- 1 | package com.sucy.enchant.data; 2 | 3 | import com.google.common.collect.ImmutableSet; 4 | import com.sucy.enchant.api.CustomEnchantment; 5 | import com.sucy.enchant.api.Enchantments; 6 | import org.bukkit.Material; 7 | import org.bukkit.entity.Player; 8 | import org.bukkit.inventory.ItemStack; 9 | import org.bukkit.inventory.PlayerInventory; 10 | 11 | import java.util.HashMap; 12 | import java.util.Map; 13 | import java.util.Set; 14 | 15 | import static com.sucy.enchant.util.Utils.isPresent; 16 | 17 | /** 18 | * EnchantmentAPI © 2017 19 | * com.sucy.enchant.data.PlayerEquips 20 | */ 21 | public class PlayerEquips { 22 | 23 | private static final ItemData EMPTY = new ItemData(); 24 | private static ItemData[] CLEAR = new ItemData[] { EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY }; 25 | 26 | private ItemData[] equipped = CLEAR; 27 | private int[] slots = new int[] { 36, 37, 38, 39, 40 }; 28 | 29 | private Map enchantments = new HashMap<>(); 30 | 31 | public PlayerEquips(final Player player) { 32 | update(player); 33 | } 34 | 35 | public Map getEnchantments() { 36 | return enchantments; 37 | } 38 | 39 | public void clear(final Player player) { 40 | swap(player, CLEAR); 41 | } 42 | 43 | public void update(final Player player) { 44 | final PlayerInventory inv = player.getInventory(); 45 | final ItemData[] updated = new ItemData[equipped.length]; 46 | for (int i = 0; i < updated.length; i++) { 47 | final ItemStack item = i < slots.length ? inv.getItem(slots[i]) : inv.getItemInMainHand(); 48 | updated[i] = ItemData.from(item); 49 | } 50 | swap(player, updated); 51 | } 52 | 53 | public void clearWeapon(final Player player) { 54 | final ItemData[] copy = new ItemData[equipped.length]; 55 | System.arraycopy(equipped, 0, copy, 0, equipped.length - 1); 56 | copy[copy.length - 1] = EMPTY; 57 | swap(player, copy); 58 | } 59 | 60 | public void updateWeapon(final PlayerInventory inventory) { 61 | final ItemData[] copy = new ItemData[equipped.length]; 62 | System.arraycopy(equipped, 0, copy, 0, equipped.length - 1); 63 | copy[copy.length - 1] = ItemData.from(inventory.getItemInMainHand()); 64 | swap((Player)inventory.getHolder(), copy); 65 | } 66 | 67 | private void swap(final Player player, final ItemData[] updatedEquips) { 68 | final Map newEnchants = new HashMap<>(); 69 | for (final ItemData updatedEquip : updatedEquips) { 70 | mergeEnchantments(updatedEquip, newEnchants); 71 | } 72 | 73 | enchantments.forEach((enchant, level) -> { 74 | if (!newEnchants.containsKey(enchant)) { 75 | enchant.applyUnequip(player, level); 76 | } 77 | }); 78 | newEnchants.forEach((enchant, level) -> { 79 | if (!enchantments.containsKey(enchant)) { 80 | enchant.applyEquip(player, level); 81 | } else { 82 | final int previous = enchantments.get(enchant); 83 | if (previous != level) { 84 | enchant.applyUnequip(player, previous); 85 | enchant.applyEquip(player, level); 86 | } 87 | } 88 | }); 89 | 90 | equipped = updatedEquips; 91 | enchantments = newEnchants; 92 | } 93 | 94 | private void mergeEnchantments(final ItemData itemData, final Map enchantments) { 95 | itemData.enchantments.forEach((enchant, level) -> { 96 | final int previous = enchantments.getOrDefault(enchant, 0); 97 | final int merged = enchant.canStack() ? previous + level : Math.max(previous, level); 98 | enchantments.put(enchant, merged); 99 | }); 100 | } 101 | 102 | private static class ItemData { 103 | private Map enchantments; 104 | 105 | private static ItemData from(final ItemStack item) { 106 | return !isPresent(item) || item.getType() == Material.ENCHANTED_BOOK ? EMPTY : new ItemData(item); 107 | } 108 | 109 | private ItemData() { 110 | enchantments = new HashMap<>(); 111 | } 112 | 113 | private ItemData(final ItemStack item) { 114 | enchantments = Enchantments.getCustomEnchantments(item); 115 | } 116 | } 117 | 118 | public static final Set ARMOR_TYPES = getArmorMaterials(); 119 | 120 | private static Set getArmorMaterials() { 121 | final Set armorSuffixes = ImmutableSet.of("BOOTS", "LEGGINGS", "CHESTPLATE", "HELMET"); 122 | final ImmutableSet.Builder builder = ImmutableSet.builder(); 123 | for (Material material : Material.values()) { 124 | final int index = material.name().lastIndexOf('_') + 1; 125 | final String suffix = material.name().substring(index); 126 | if (armorSuffixes.contains(suffix)) { 127 | builder.add(material); 128 | } 129 | } 130 | return builder.build(); 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /src/com/sucy/enchant/listener/AnvilListener.java: -------------------------------------------------------------------------------- 1 | package com.sucy.enchant.listener; 2 | 3 | import com.rit.sucy.text.TextFormatter; 4 | import com.sucy.enchant.data.ConfigKey; 5 | import com.sucy.enchant.data.Configuration; 6 | import com.sucy.enchant.mechanics.EnchantmentMerger; 7 | import org.bukkit.ChatColor; 8 | import org.bukkit.GameMode; 9 | import org.bukkit.Material; 10 | import org.bukkit.entity.Player; 11 | import org.bukkit.event.EventHandler; 12 | import org.bukkit.event.inventory.InventoryClickEvent; 13 | import org.bukkit.event.inventory.InventoryType; 14 | import org.bukkit.event.inventory.PrepareAnvilEvent; 15 | import org.bukkit.inventory.AnvilInventory; 16 | import org.bukkit.inventory.ItemStack; 17 | import org.bukkit.inventory.meta.ItemMeta; 18 | 19 | import static com.sucy.enchant.util.Utils.isPresent; 20 | 21 | /** 22 | * EnchantmentAPI © 2017 23 | * com.sucy.enchant.listener.AnvilListener 24 | */ 25 | public class AnvilListener extends BaseListener { 26 | 27 | private final boolean colored = Configuration.using(ConfigKey.COLORED_NAMES_IN_ANVILS); 28 | 29 | @EventHandler 30 | public void onCombine(final PrepareAnvilEvent event) { 31 | final ItemStack first = event.getInventory().getItem(0); 32 | final ItemStack second = event.getInventory().getItem(1); 33 | if (isSingle(first) && isSingle(second)) { 34 | final ItemStack result = select(first, second, true).clone(); 35 | final EnchantmentMerger merger = getMerger(event.getInventory()); 36 | if (merger.getCustomCost() != 0 || merger.getVanillaCost() != 0) { 37 | event.setResult(merger.apply(result)); 38 | event.getInventory().setRepairCost(merger.getCost()); 39 | } 40 | } 41 | 42 | if (isPresent(event.getResult())) { 43 | String text = event.getInventory().getRenameText(); 44 | 45 | final ItemStack primary = select(first, second, true); 46 | if (primary.hasItemMeta()) { 47 | final ItemMeta primaryMeta = primary.getItemMeta(); 48 | if (primaryMeta.hasDisplayName()) { 49 | final String withoutColorChar = primaryMeta.getDisplayName().replace("" + ChatColor.COLOR_CHAR, ""); 50 | if (withoutColorChar.equals(event.getInventory().getRenameText())) { 51 | text = primaryMeta.getDisplayName(); 52 | } 53 | } 54 | } 55 | 56 | 57 | final ItemMeta meta = event.getResult().getItemMeta(); 58 | meta.setDisplayName(colored ? TextFormatter.colorString(text) : text); 59 | event.getResult().setItemMeta(meta); 60 | } 61 | } 62 | 63 | private EnchantmentMerger getMerger(final AnvilInventory anvil) { 64 | final ItemStack result = select(anvil.getItem(0), anvil.getItem(1), true).clone(); 65 | final ItemStack supplement = select(anvil.getItem(0), anvil.getItem(1), false); 66 | return new EnchantmentMerger() 67 | .merge(result, false) 68 | .merge(supplement, true); 69 | } 70 | 71 | @EventHandler 72 | public void onClick(InventoryClickEvent event) { 73 | if (event.getInventory().getType() == InventoryType.ANVIL && event.getRawSlot() == 2) { 74 | final AnvilInventory anvil = (AnvilInventory) event.getInventory(); 75 | if (anvil.getRepairCost() == 0 76 | && isPresent(anvil.getItem(2)) 77 | && !isPresent(event.getWhoClicked().getItemOnCursor()) || !event.getAction().name().startsWith("PICKUP")) { 78 | final Player player = (Player)event.getWhoClicked(); 79 | if (player.getGameMode() == GameMode.CREATIVE || checkLevels(player, anvil)) { 80 | player.setItemOnCursor(anvil.getItem(2)); 81 | anvil.clear(); 82 | } 83 | } 84 | } 85 | } 86 | 87 | private boolean checkLevels(final Player player, final AnvilInventory anvil) { 88 | final EnchantmentMerger merger = getMerger(anvil); 89 | if (player.getLevel() >= merger.getCost()) { 90 | player.setLevel(player.getLevel() - merger.getCost()); 91 | return true; 92 | } 93 | return false; 94 | } 95 | 96 | private ItemStack select(final ItemStack first, final ItemStack second, final boolean result) { 97 | return (!isBook(first) || isBook(second)) == result ? first : second; 98 | } 99 | 100 | private boolean isBook(final ItemStack item) { 101 | return !isPresent(item) || item.getType() == Material.BOOK || item.getType() == Material.ENCHANTED_BOOK; 102 | } 103 | 104 | private boolean isSingle(final ItemStack item) { 105 | return isPresent(item) && item.getAmount() == 1; 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/com/sucy/enchant/listener/BaseListener.java: -------------------------------------------------------------------------------- 1 | package com.sucy.enchant.listener; 2 | 3 | import com.sucy.enchant.EnchantmentAPI; 4 | import org.bukkit.entity.LivingEntity; 5 | import org.bukkit.entity.Projectile; 6 | import org.bukkit.event.Listener; 7 | import org.bukkit.event.entity.EntityDamageByEntityEvent; 8 | 9 | /** 10 | * EnchantmentAPI © 2017 11 | * com.sucy.enchant.listener.BaseListener 12 | */ 13 | public abstract class BaseListener implements Listener { 14 | public void init(final EnchantmentAPI plugin) { } 15 | public void cleanUp(final EnchantmentAPI plugin) { } 16 | 17 | /** 18 | * Retrieves a damager from an entity damage event which will get the 19 | * shooter of projectiles if it was a projectile hitting them or 20 | * converts the Entity damager to a LivingEntity if applicable. 21 | * 22 | * @param event event to grab the damager from 23 | * 24 | * @return LivingEntity damager of the event or null if not found 25 | */ 26 | LivingEntity getDamager(final EntityDamageByEntityEvent event) 27 | { 28 | if (event.getDamager() instanceof LivingEntity) 29 | { 30 | return (LivingEntity) event.getDamager(); 31 | } 32 | else if (event.getDamager() instanceof Projectile) 33 | { 34 | final Projectile projectile = (Projectile) event.getDamager(); 35 | if (projectile.getShooter() instanceof LivingEntity) 36 | { 37 | return (LivingEntity) projectile.getShooter(); 38 | } 39 | } 40 | return null; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/com/sucy/enchant/listener/EnchantListener.java: -------------------------------------------------------------------------------- 1 | package com.sucy.enchant.listener; 2 | 3 | import com.google.common.collect.ImmutableList; 4 | import com.google.common.collect.ImmutableSet; 5 | import com.rit.sucy.config.CommentedConfig; 6 | import com.rit.sucy.config.parse.DataSection; 7 | import com.rit.sucy.items.ItemManager; 8 | import com.sucy.enchant.EnchantmentAPI; 9 | import com.sucy.enchant.api.Enchantments; 10 | import com.sucy.enchant.api.GlowEffects; 11 | import com.sucy.enchant.api.ItemSet; 12 | import com.sucy.enchant.api.Tasks; 13 | import com.sucy.enchant.data.ConfigKey; 14 | import com.sucy.enchant.data.Configuration; 15 | import com.sucy.enchant.data.Path; 16 | import com.sucy.enchant.mechanics.EnchantResult; 17 | import com.sucy.enchant.mechanics.EnchantingMechanics; 18 | import org.bukkit.ChatColor; 19 | import org.bukkit.GameMode; 20 | import org.bukkit.Material; 21 | import org.bukkit.enchantments.EnchantmentOffer; 22 | import org.bukkit.entity.Player; 23 | import org.bukkit.event.EventHandler; 24 | import org.bukkit.event.EventPriority; 25 | import org.bukkit.event.enchantment.EnchantItemEvent; 26 | import org.bukkit.event.enchantment.PrepareItemEnchantEvent; 27 | import org.bukkit.event.inventory.InventoryClickEvent; 28 | import org.bukkit.event.inventory.InventoryCloseEvent; 29 | import org.bukkit.event.inventory.InventoryType; 30 | import org.bukkit.event.player.PlayerJoinEvent; 31 | import org.bukkit.event.player.PlayerQuitEvent; 32 | import org.bukkit.inventory.ItemStack; 33 | import org.bukkit.inventory.meta.ItemMeta; 34 | 35 | import java.util.ArrayList; 36 | import java.util.HashMap; 37 | import java.util.List; 38 | import java.util.Map; 39 | import java.util.Random; 40 | import java.util.Set; 41 | import java.util.UUID; 42 | 43 | import static com.sucy.enchant.util.Utils.isPresent; 44 | 45 | /** 46 | * EnchantmentAPI © 2017 47 | * com.sucy.enchant.listener.EnchantListener 48 | */ 49 | public class EnchantListener extends BaseListener { 50 | 51 | private static final String DATA_FILE = "seeds"; 52 | 53 | private final Map enchantSeeds = new HashMap<>(); 54 | private final Map placeholders = new HashMap<>(); 55 | private final Map offers = new HashMap<>(); 56 | 57 | private final EnchantingMechanics mechanics = new EnchantingMechanics(); 58 | 59 | private final Random random = new Random(); 60 | 61 | private final boolean nonEnchantables = Configuration.using(ConfigKey.NON_ENCHANTABLES); 62 | 63 | @Override 64 | public void init(final EnchantmentAPI plugin) { 65 | final DataSection data = new CommentedConfig(plugin, Path.DATA_FOLDER + DATA_FILE).getConfig(); 66 | data.keys().forEach(key -> enchantSeeds.put(UUID.fromString(key), Long.parseLong(data.getString(key)))); 67 | } 68 | 69 | @Override 70 | public void cleanUp(final EnchantmentAPI plugin) { 71 | final CommentedConfig config = new CommentedConfig(plugin, Path.DATA_FOLDER + DATA_FILE); 72 | final DataSection data = config.getConfig(); 73 | data.clear(); 74 | 75 | enchantSeeds.forEach((uuid, seed) -> data.set(uuid.toString(), Long.toString(seed))); 76 | config.save(); 77 | } 78 | 79 | @EventHandler 80 | public void onJoin(final PlayerJoinEvent event) { 81 | enchantSeeds.computeIfAbsent(event.getPlayer().getUniqueId(), uuid -> random.nextLong()); 82 | } 83 | 84 | @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) 85 | public void onPrepareEnchant(final PrepareItemEnchantEvent event) { 86 | final ItemStack item = getItem(event.getItem(), event.getEnchanter()); 87 | final long seed = enchantSeeds.get(event.getEnchanter().getUniqueId()); 88 | offers.put(event.getEnchanter().getUniqueId(), transformOffers(event.getOffers())); 89 | for (final EnchantmentOffer offer : event.getOffers()) { 90 | if (offer == null) continue; 91 | final EnchantResult result = mechanics.generateEnchantments( 92 | event.getEnchanter(), item, offer.getCost(), true, seed); 93 | result.getFirstVanillaEnchantment().ifPresent(enchant -> { 94 | offer.setEnchantment(enchant.getEnchantment()); 95 | offer.setEnchantmentLevel(result.getFirstVanillaLevel()); 96 | }); 97 | } 98 | } 99 | 100 | private int[] transformOffers(final EnchantmentOffer[] offers) { 101 | final List result = new ArrayList<>(6); 102 | for (int i = 0; i < offers.length; i++) { 103 | if (offers[i] == null) continue; 104 | 105 | result.add(offers[i].getCost()); 106 | result.add(i + 1); 107 | } 108 | return result.stream().mapToInt(Integer::intValue).toArray(); 109 | } 110 | 111 | private ItemStack getItem(final ItemStack fromEvent, final Player enchanter) { 112 | return placeholders.getOrDefault(enchanter.getUniqueId(), fromEvent); 113 | } 114 | 115 | @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) 116 | public void onEnchant(final EnchantItemEvent event) { 117 | final ItemStack item = getItem(event.getItem(), event.getEnchanter()); 118 | final long seed = enchantSeeds.get(event.getEnchanter().getUniqueId()); 119 | final EnchantResult result = mechanics.generateEnchantments( 120 | event.getEnchanter(), item, event.getExpLevelCost(), true, seed); 121 | 122 | placeholders.remove(event.getEnchanter().getUniqueId()); 123 | event.getEnchantsToAdd().clear(); 124 | result.getEnchantments().forEach((enchant, level) -> enchant.addToItem(item, level)); 125 | GlowEffects.finalize(item); 126 | enchantSeeds.put(event.getEnchanter().getUniqueId(), random.nextLong()); 127 | event.getInventory().setItem(0, item); 128 | event.setCancelled(true); 129 | 130 | if (event.getEnchanter().getGameMode() != GameMode.CREATIVE) { 131 | int cost = 0; 132 | final int[] tiers = offers.get(event.getEnchanter().getUniqueId()); 133 | for (int i = 0; i < tiers.length; i += 2) { 134 | if (tiers[i] == event.getExpLevelCost()) cost = tiers[i + 1]; 135 | } 136 | event.getEnchanter().setLevel(event.getEnchanter().getLevel() - cost); 137 | event.getInventory().removeItem(new ItemStack(ItemSet.INK_SACK.getItems()[0], cost, (short) 4)); 138 | } 139 | } 140 | 141 | @EventHandler 142 | public void onClick(final InventoryClickEvent event) { 143 | if (!nonEnchantables) { 144 | return; 145 | } 146 | 147 | if (event.getInventory().getType() == InventoryType.ENCHANTING) { 148 | if (event.getRawSlot() == 0 && placeholders.containsKey(event.getWhoClicked().getUniqueId())) { 149 | event.getInventory().setItem(0, placeholders.remove(event.getWhoClicked().getUniqueId())); 150 | } 151 | 152 | Tasks.schedule(() -> { 153 | final ItemStack item = event.getInventory().getItem(0); 154 | if (isPresent(item) && !ENCHANTABLES.contains(item.getType()) 155 | && Enchantments.getAllEnchantments(item).isEmpty() 156 | && mechanics.hasValidEnchantments(item) 157 | && !placeholders.containsKey(event.getWhoClicked().getUniqueId())) { 158 | placeholders.put(event.getWhoClicked().getUniqueId(), item); 159 | event.getInventory().setItem(0, wrap(item)); 160 | } 161 | }); 162 | } 163 | } 164 | 165 | @EventHandler 166 | public void onClose(final InventoryCloseEvent event) { 167 | if (placeholders.containsKey(event.getPlayer().getUniqueId())) { 168 | event.getInventory().setItem(0, placeholders.remove(event.getPlayer().getUniqueId())); 169 | } 170 | } 171 | 172 | @EventHandler 173 | public void onQuit(final PlayerQuitEvent event) { 174 | offers.remove(event.getPlayer().getUniqueId()); 175 | if (placeholders.containsKey(event.getPlayer().getUniqueId())) { 176 | event.getPlayer().closeInventory(); 177 | } 178 | } 179 | 180 | private ItemStack wrap(final ItemStack item) { 181 | final ItemStack wrapper = new ItemStack(Material.BOOK); 182 | final ItemMeta meta = wrapper.getItemMeta(); 183 | meta.setDisplayName(ChatColor.LIGHT_PURPLE + "Enchanting"); 184 | meta.setLore(ImmutableList.of(ItemManager.getVanillaName(item))); 185 | wrapper.setItemMeta(meta); 186 | return wrapper; 187 | } 188 | 189 | private static final Set ENCHANTABLES = ImmutableSet.builder() 190 | .add(ItemSet.VANILLA_ENCHANTABLES.getItems()) 191 | .add(Material.BOOK) 192 | .add(Material.ENCHANTED_BOOK) 193 | .build(); 194 | } 195 | -------------------------------------------------------------------------------- /src/com/sucy/enchant/listener/FishingListener.java: -------------------------------------------------------------------------------- 1 | package com.sucy.enchant.listener; 2 | 3 | import com.sucy.enchant.api.Enchantments; 4 | import com.sucy.enchant.data.ConfigKey; 5 | import com.sucy.enchant.data.Configuration; 6 | import com.sucy.enchant.mechanics.EnchantingMechanics; 7 | import org.bukkit.entity.Item; 8 | import org.bukkit.event.EventHandler; 9 | import org.bukkit.event.player.PlayerFishEvent; 10 | import org.bukkit.inventory.ItemStack; 11 | 12 | import java.util.Random; 13 | 14 | /** 15 | * EnchantmentAPI © 2017 16 | * com.sucy.enchant.listener.FishingListener 17 | */ 18 | public class FishingListener extends BaseListener { 19 | 20 | private final Random random = new Random(); 21 | 22 | private final EnchantingMechanics mechanics = new EnchantingMechanics(); 23 | 24 | private final int bookLevel = Configuration.amount(ConfigKey.FISHING_ENCHANTING_LEVEL); 25 | 26 | @EventHandler 27 | public void onCatch(final PlayerFishEvent event) { 28 | if (!(event.getCaught() instanceof Item)) 29 | return; 30 | 31 | final Item caught = (Item)event.getCaught(); 32 | final ItemStack item = caught.getItemStack(); 33 | if (!Enchantments.getAllEnchantments(item).isEmpty()) { 34 | Enchantments.removeAllEnchantments(item); 35 | mechanics.generateEnchantments(event.getPlayer(), item, bookLevel, false, random.nextLong()) 36 | .getEnchantments().forEach((enchant, level) -> enchant.addToItem(item, level)); 37 | caught.setItemStack(item); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/com/sucy/enchant/listener/ItemListener.java: -------------------------------------------------------------------------------- 1 | package com.sucy.enchant.listener; 2 | 3 | import com.sucy.enchant.api.Enchantments; 4 | import com.sucy.enchant.api.Tasks; 5 | import com.sucy.enchant.data.PlayerEquips; 6 | import org.bukkit.entity.LivingEntity; 7 | import org.bukkit.entity.Player; 8 | import org.bukkit.event.EventHandler; 9 | import org.bukkit.event.EventPriority; 10 | import org.bukkit.event.block.Action; 11 | import org.bukkit.event.block.BlockBreakEvent; 12 | import org.bukkit.event.entity.EntityDamageByEntityEvent; 13 | import org.bukkit.event.entity.EntityDamageEvent; 14 | import org.bukkit.event.entity.EntityPickupItemEvent; 15 | import org.bukkit.event.entity.ProjectileLaunchEvent; 16 | import org.bukkit.event.inventory.InventoryCloseEvent; 17 | import org.bukkit.event.player.PlayerDropItemEvent; 18 | import org.bukkit.event.player.PlayerInteractEntityEvent; 19 | import org.bukkit.event.player.PlayerInteractEvent; 20 | import org.bukkit.event.player.PlayerItemBreakEvent; 21 | import org.bukkit.event.player.PlayerItemHeldEvent; 22 | import org.bukkit.event.player.PlayerJoinEvent; 23 | import org.bukkit.event.player.PlayerQuitEvent; 24 | import org.bukkit.inventory.ItemStack; 25 | 26 | import java.util.HashMap; 27 | import java.util.UUID; 28 | 29 | import static com.sucy.enchant.util.Utils.isPresent; 30 | 31 | /** 32 | * EnchantmentAPI © 2017 33 | * com.sucy.enchant.listener.ItemListener 34 | */ 35 | public class ItemListener extends BaseListener { 36 | 37 | private static final HashMap LAST_INTERACT_BLOCK = new HashMap<>(); 38 | private static final HashMap LAST_INTERACT_ENTITY = new HashMap<>(); 39 | 40 | private static final int INTERACT_DELAY_MILLIS = 250; 41 | 42 | // ---- Tracking enchantments ---- // 43 | 44 | @EventHandler 45 | public void onJoin(final PlayerJoinEvent event) { 46 | Enchantments.getEquipmentData(event.getPlayer()); 47 | } 48 | 49 | @EventHandler 50 | public void onQuit(final PlayerQuitEvent event) { 51 | Enchantments.clearEquipmentData(event.getPlayer()); 52 | LAST_INTERACT_BLOCK.remove(event.getPlayer().getUniqueId()); 53 | LAST_INTERACT_ENTITY.remove(event.getPlayer().getUniqueId()); 54 | } 55 | 56 | @EventHandler 57 | public void onDrop(final PlayerDropItemEvent event) { 58 | Enchantments.getEquipmentData(event.getPlayer()).clearWeapon(event.getPlayer()); 59 | } 60 | 61 | @EventHandler 62 | public void onBreak(final PlayerItemBreakEvent event) { 63 | final PlayerEquips equips = Enchantments.getEquipmentData(event.getPlayer()); 64 | Tasks.schedule(() -> equips.updateWeapon(event.getPlayer().getInventory())); 65 | } 66 | 67 | @EventHandler 68 | public void onPickup(final EntityPickupItemEvent event) { 69 | if (event.getEntity() instanceof Player) { 70 | final PlayerEquips equips = Enchantments.getEquipmentData((Player) event.getEntity()); 71 | Tasks.schedule(() -> equips.updateWeapon(((Player) event.getEntity()).getInventory())); 72 | } 73 | } 74 | 75 | @EventHandler 76 | public void onHeld(final PlayerItemHeldEvent event) { 77 | final PlayerEquips equips = Enchantments.getEquipmentData(event.getPlayer()); 78 | Tasks.schedule(() -> equips.updateWeapon(event.getPlayer().getInventory())); 79 | } 80 | 81 | @EventHandler 82 | public void onClose(final InventoryCloseEvent event) { 83 | final PlayerEquips equips = Enchantments.getEquipmentData((Player) event.getPlayer()); 84 | Tasks.schedule(() -> equips.update((Player) event.getPlayer())); 85 | } 86 | 87 | @EventHandler 88 | public void onInteract(final PlayerInteractEvent event) { 89 | final ItemStack item = event.getPlayer().getInventory().getItemInMainHand(); 90 | if ((event.getAction() == Action.RIGHT_CLICK_AIR || event.getAction() == Action.RIGHT_CLICK_BLOCK) 91 | && isPresent(item) && PlayerEquips.ARMOR_TYPES.contains(item.getType())) { 92 | final PlayerEquips equips = Enchantments.getEquipmentData(event.getPlayer()); 93 | Tasks.schedule(() -> equips.update(event.getPlayer())); 94 | } 95 | } 96 | 97 | // ---- API Methods ---- // 98 | 99 | private boolean running = false; 100 | 101 | @EventHandler(ignoreCancelled = true, priority = EventPriority.HIGH) 102 | public void onHit(final EntityDamageByEntityEvent event) { 103 | if (running || event.getCause() == EntityDamageEvent.DamageCause.CUSTOM) { 104 | return; 105 | } 106 | 107 | running = true; 108 | try { 109 | final LivingEntity damager = getDamager(event); 110 | if (damager instanceof Player && event.getEntity() instanceof LivingEntity) { 111 | Enchantments.getEnchantments((Player) damager).forEach( 112 | (enchant, level) -> enchant.applyOnHit( 113 | damager, 114 | (LivingEntity) event.getEntity(), 115 | level, 116 | event)); 117 | } 118 | 119 | if (event.getEntity() instanceof Player && damager != null) { 120 | Enchantments.getEnchantments((Player) event.getEntity()).forEach( 121 | (enchant, level) -> enchant.applyDefense((Player) event.getEntity(), damager, level, event)); 122 | } 123 | } catch (final Exception ex) { 124 | ex.printStackTrace(); 125 | } 126 | running = false; 127 | } 128 | 129 | @EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true) 130 | public void onShoot(final ProjectileLaunchEvent event) { 131 | if (!(event.getEntity().getShooter() instanceof Player)) { 132 | return; 133 | } 134 | final Player shooter = (Player) event.getEntity().getShooter(); 135 | Enchantments.getEnchantments(shooter) 136 | .forEach((enchant, level) -> enchant.applyProjectile(shooter, level, event)); 137 | } 138 | 139 | @EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true) 140 | public void onBreak(final BlockBreakEvent event) { 141 | Enchantments.getEnchantments(event.getPlayer()).forEach( 142 | (enchant, level) -> enchant.applyBreak(event.getPlayer(), event.getBlock(), level, event)); 143 | } 144 | 145 | @EventHandler(priority = EventPriority.HIGH) 146 | public void onInteractBlock(final PlayerInteractEvent event) { 147 | if (LAST_INTERACT_BLOCK.getOrDefault(event.getPlayer().getUniqueId(), 0L) > System.currentTimeMillis()) { 148 | return; 149 | } 150 | 151 | LAST_INTERACT_BLOCK.put(event.getPlayer().getUniqueId(), System.currentTimeMillis() + INTERACT_DELAY_MILLIS); 152 | Enchantments.getEnchantments(event.getPlayer()).forEach( 153 | (enchant, level) -> enchant.applyInteractBlock(event.getPlayer(), level, event)); 154 | } 155 | 156 | @EventHandler(priority = EventPriority.HIGH) 157 | public void onInteractEntity(final PlayerInteractEntityEvent event) { 158 | if (LAST_INTERACT_ENTITY.getOrDefault(event.getPlayer().getUniqueId(), 0L) > System.currentTimeMillis()) { 159 | return; 160 | } 161 | 162 | LAST_INTERACT_ENTITY.put(event.getPlayer().getUniqueId(), System.currentTimeMillis() + INTERACT_DELAY_MILLIS); 163 | Enchantments.getEnchantments(event.getPlayer()).forEach( 164 | (enchant, level) -> enchant.applyInteractEntity(event.getPlayer(), level, event)); 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /src/com/sucy/enchant/mechanics/EnchantResult.java: -------------------------------------------------------------------------------- 1 | package com.sucy.enchant.mechanics; 2 | 3 | import com.sucy.enchant.api.CustomEnchantment; 4 | import com.sucy.enchant.vanilla.VanillaEnchantment; 5 | 6 | import java.util.HashMap; 7 | import java.util.Map; 8 | import java.util.Optional; 9 | 10 | /** 11 | * EnchantmentAPI © 2017 12 | * com.sucy.enchant.mechanics.EnchantResult 13 | */ 14 | public class EnchantResult { 15 | private final Map enchantments = new HashMap<>(); 16 | 17 | private VanillaEnchantment firstVanilla; 18 | private int firstVanillaLevel; 19 | private int enchantLevel; 20 | private int enchantabilityModifier; 21 | 22 | public EnchantResult(final int enchantLevel, final int modifier) { 23 | this.enchantLevel = enchantLevel; 24 | this.enchantabilityModifier = modifier; 25 | } 26 | 27 | void addEnchantment(final CustomEnchantment enchant, final Integer level) { 28 | if (enchant instanceof VanillaEnchantment) { 29 | firstVanilla = (VanillaEnchantment) enchant; 30 | firstVanillaLevel = level; 31 | } 32 | enchantments.put(enchant, level); 33 | } 34 | 35 | public int getEnchantabilityModifier() { 36 | return enchantabilityModifier; 37 | } 38 | 39 | public int getEnchantLevel() { 40 | return enchantLevel; 41 | } 42 | 43 | public Optional getFirstVanillaEnchantment() { 44 | return Optional.ofNullable(firstVanilla); 45 | } 46 | 47 | public int getFirstVanillaLevel() { 48 | return firstVanillaLevel; 49 | } 50 | 51 | public Map getEnchantments() { 52 | return enchantments; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/com/sucy/enchant/mechanics/EnchantingMechanics.java: -------------------------------------------------------------------------------- 1 | package com.sucy.enchant.mechanics; 2 | 3 | import com.sucy.enchant.EnchantmentAPI; 4 | import com.sucy.enchant.api.CustomEnchantment; 5 | import com.sucy.enchant.data.ConfigKey; 6 | import com.sucy.enchant.data.Configuration; 7 | import com.sucy.enchant.data.Enchantability; 8 | import org.bukkit.Material; 9 | import org.bukkit.entity.Player; 10 | import org.bukkit.inventory.ItemStack; 11 | import org.bukkit.permissions.Permissible; 12 | 13 | import java.util.Collection; 14 | import java.util.HashMap; 15 | import java.util.List; 16 | import java.util.Map; 17 | import java.util.Random; 18 | import java.util.stream.Collectors; 19 | 20 | /** 21 | * EnchantmentAPI © 2017 22 | * com.sucy.enchant.mechanics.EnchantingMechanics 23 | */ 24 | public class EnchantingMechanics { 25 | 26 | private final int maxEnchants = Configuration.amount(ConfigKey.MAX_ENCHANTMENTS); 27 | 28 | /** 29 | * Generates an enchantment result for the given item 30 | * 31 | * @param enchanter player enchanting the item (use null to bypass permissions) 32 | * @param item item to enchant 33 | * @param enchantLevel enchantment level (1-30 from the table) 34 | * @param table whether or not it is a table enchantment. If not a table enchantment maxLevel is used over maxTableLevel 35 | * @param seed enchanter's random seed for enchanting 36 | * @return enchanting result 37 | */ 38 | public EnchantResult generateEnchantments( 39 | final Player enchanter, final ItemStack item, final int enchantLevel, final boolean table, final long seed) { 40 | 41 | final Random random = new Random(seed); 42 | 43 | final int modifier = getModifier(Enchantability.determine(item.getType()), random); 44 | final int adjustedLevel = modifyLevel(enchantLevel, modifier, random); 45 | final EnchantResult result = new EnchantResult(adjustedLevel, modifier); 46 | 47 | List available = getAllValidEnchants(item, enchanter, adjustedLevel, table); 48 | if (available.isEmpty()) { 49 | return result; 50 | } 51 | 52 | CustomEnchantment toAdd = weightedRandom(available, item.getType(), random); 53 | result.addEnchantment(toAdd, toAdd.computeLevel(adjustedLevel)); 54 | int repeatFactor = adjustedLevel; 55 | while (random.nextInt(50) <= repeatFactor && result.getEnchantments().size() < maxEnchants) { 56 | final CustomEnchantment previous = toAdd; 57 | available = available.stream() 58 | .filter(enchant -> !previous.conflictsWith(enchant, true)) 59 | .collect(Collectors.toList()); 60 | if (available.isEmpty()) { 61 | break; 62 | } 63 | 64 | toAdd = weightedRandom(available, item.getType(), random); 65 | result.addEnchantment(toAdd, toAdd.computeLevel(adjustedLevel)); 66 | repeatFactor >>= 1; 67 | } 68 | 69 | return result; 70 | } 71 | 72 | private CustomEnchantment weightedRandom( 73 | final Collection enchantments, final Material material, final Random random) { 74 | 75 | final double totalWeight = enchantments.stream() 76 | .mapToDouble(enchant -> enchant.getWeight(material)) 77 | .sum(); 78 | 79 | double roll = random.nextDouble() * totalWeight; 80 | for (final CustomEnchantment enchantment : enchantments) { 81 | roll -= enchantment.getWeight(material); 82 | if (roll < 0) { 83 | return enchantment; 84 | } 85 | } 86 | throw new IllegalStateException("Should not call weighted random without any entries"); 87 | } 88 | 89 | private int getModifier(final int enchantability, final Random random) { 90 | final int modifiedEnchantability = enchantability / 4 + 1; 91 | return random.nextInt(modifiedEnchantability) + random.nextInt(modifiedEnchantability); 92 | } 93 | 94 | private int modifyLevel(final int level, final int modifier, final Random random) { 95 | final int modified = level + 1 + modifier; 96 | final float percentage = 0.85f + (random.nextFloat() + random.nextFloat()) * 0.15f; 97 | return (int)Math.max(1, modified * percentage + 0.5f); 98 | } 99 | 100 | /** 101 | * Get all enchantments which can be applied to the given ItemStack 102 | * 103 | * @param item item to check for applicable enchantments 104 | * @param level modified enchantment level 105 | * @return List of all applicable enchantments 106 | */ 107 | public List getAllValidEnchants(final ItemStack item, final Permissible enchanter, final int level, final boolean table) { 108 | return EnchantmentAPI.getRegisteredEnchantments().stream() 109 | .filter(CustomEnchantment::isEnabled) 110 | .filter(enchant -> !table || enchant.isTableEnabled()) 111 | .filter(enchant -> item.getType() == Material.BOOK || enchant.canEnchantOnto(item)) 112 | .filter(enchant -> enchant.hasPermission(enchanter)) 113 | .filter(enchant -> enchant.computeLevel(level) >= 1) 114 | .collect(Collectors.toList()); 115 | } 116 | 117 | public boolean hasValidEnchantments(final ItemStack item) { 118 | return EnchantmentAPI.getRegisteredEnchantments().stream() 119 | .anyMatch(enchant -> enchant.canEnchantOnto(item)); 120 | } 121 | 122 | // --- Computing statistics --- // 123 | 124 | public int maxLevel(final int level, final int enchantability) { 125 | return (int)Math.max(1, (level + 1 + enchantability / 2) * 1.15f + 0.5f); 126 | } 127 | 128 | public Map enchantabilitySpread(final int enchantability) { 129 | final int val = enchantability / 4 + 1; 130 | final Map result = new HashMap<>(val * 2 - 1); 131 | for (int i = 0; i < val; i++) { 132 | result.put(i, i + 1); 133 | result.put(val * 2 - 2 - i, i + 1); 134 | } 135 | return result; 136 | } 137 | 138 | public int enchantabilityWeight(final int enchantability) { 139 | final int val = enchantability / 4 + 1; 140 | return val * val; 141 | } 142 | 143 | public double chance(final int level, final CustomEnchantment enchant, final int target) { 144 | final int min = (int)Math.ceil(enchant.getMinEnchantingLevel() + enchant.getEnchantLevelScaleFactor() * (target - 1)); 145 | final int max = (int)Math.ceil(enchant.getMinEnchantingLevel() + enchant.getEnchantLevelScaleFactor() * target 146 | + (target == enchant.getMaxTableLevel() ? enchant.getEnchantLevelBuffer() : 0)); 147 | 148 | if (min == max) { 149 | return 0; 150 | } 151 | 152 | final double minRand = Math.max(0, Math.min(1, 0.5 + (min - level) / 0.3)); 153 | final double maxRand = Math.max(0, Math.min(1, 0.5 + (max - level) / 0.3)); 154 | 155 | return triangularChance(maxRand) - triangularChance(minRand); 156 | } 157 | 158 | // Compute chance using symmetric triangular distribution formula 159 | // https://en.wikipedia.org/wiki/Triangular_distribution#Symmetric_triangular_distribution 160 | // 2x^2 (0 < x < 0.5), 2x^2 - (2x - 1)^2 (0.5 < x < 1) 161 | private double triangularChance(final double rand) { 162 | if (rand <= 0.5) return 2 * (rand * rand); 163 | final double sub = 2 * rand - 1; 164 | return 2 * (rand * rand) - sub * sub; 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /src/com/sucy/enchant/mechanics/EnchantmentMerger.java: -------------------------------------------------------------------------------- 1 | package com.sucy.enchant.mechanics; 2 | 3 | import com.sucy.enchant.api.CustomEnchantment; 4 | import com.sucy.enchant.api.Enchantments; 5 | import com.sucy.enchant.api.GlowEffects; 6 | import com.sucy.enchant.data.ConfigKey; 7 | import com.sucy.enchant.data.Configuration; 8 | import com.sucy.enchant.vanilla.VanillaEnchantment; 9 | import org.bukkit.Material; 10 | import org.bukkit.inventory.ItemStack; 11 | 12 | import java.util.HashMap; 13 | import java.util.Map; 14 | 15 | /** 16 | * EnchantmentAPI © 2017 17 | * com.sucy.enchant.mechanics.EnchantmentMerger 18 | */ 19 | public class EnchantmentMerger { 20 | 21 | private final Map enchants; 22 | 23 | private int customCost; 24 | private int vanillaCost; 25 | private int repairCost = 0; 26 | 27 | private final int maxEnchants = Configuration.amount(ConfigKey.MAX_MERGED_ENCHANTMENTS); 28 | private final int maxLevel = Configuration.amount(ConfigKey.GLOBAL_ANVIL_LEVEL); 29 | 30 | public EnchantmentMerger() { 31 | enchants = new HashMap<>(); 32 | } 33 | 34 | public int getCost() { 35 | return repairCost + customCost + vanillaCost; 36 | } 37 | 38 | public int getVanillaCost() { 39 | return vanillaCost; 40 | } 41 | 42 | public int getCustomCost() { 43 | return customCost; 44 | } 45 | 46 | public EnchantmentMerger merge(final ItemStack item, final boolean addCost) { 47 | if (item.getDurability() < item.getType().getMaxDurability()) { 48 | repairCost = 2; 49 | } 50 | 51 | Enchantments.getAllEnchantments(item).forEach( 52 | (enchant, level) -> this.merge(enchant, level, addCost, item.getType() == Material.ENCHANTED_BOOK)); 53 | return this; 54 | } 55 | 56 | public void merge(final CustomEnchantment enchantment, final int level, final boolean addCost, final boolean book) { 57 | 58 | // Ignore conflicting enchantments 59 | if (enchants.keySet().stream().anyMatch(enchant -> enchant.conflictsWith(enchantment, false)) 60 | || (enchants.containsKey(enchantment) && enchants.size() == maxEnchants)) { 61 | if (addCost) { 62 | vanillaCost++; 63 | } 64 | return; 65 | } 66 | 67 | final int primaryLevel = enchants.getOrDefault(enchantment, 0); 68 | final int combinedLevel = primaryLevel == level 69 | ? Math.min(Math.max(maxLevel, enchantment.getMaxLevel()), level + 1) 70 | : Math.max(primaryLevel, level); 71 | enchants.put(enchantment, combinedLevel); 72 | 73 | if (addCost) { 74 | final int costPerLevel = Math.max(1, enchantment.getCombineCostPerLevel() >> (book ? 1 : 0)); 75 | if (enchantment instanceof VanillaEnchantment) { 76 | final int vanillaMax = ((VanillaEnchantment) enchantment).getEnchantment().getMaxLevel(); 77 | vanillaCost += Math.min(vanillaMax, combinedLevel) * costPerLevel; 78 | customCost += Math.max(0, combinedLevel - vanillaMax) * costPerLevel; 79 | } 80 | else { 81 | customCost += combinedLevel * costPerLevel; 82 | } 83 | } 84 | } 85 | 86 | public ItemStack apply(final ItemStack item) { 87 | final ItemStack result = Enchantments.removeAllEnchantments(item); 88 | enchants.entrySet().stream() 89 | .filter(e -> e.getKey().canMergeOnto(item)) 90 | .forEach(e -> e.getKey().addToItem(result, e.getValue())); 91 | return GlowEffects.finalize(result); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/com/sucy/enchant/skillapi/SkillAPIHook.java: -------------------------------------------------------------------------------- 1 | package com.sucy.enchant.skillapi; 2 | 3 | import com.google.common.collect.ImmutableList; 4 | import com.rit.sucy.config.CommentedConfig; 5 | import com.sucy.enchant.EnchantmentAPI; 6 | import com.sucy.enchant.api.CustomEnchantment; 7 | import org.bukkit.Bukkit; 8 | 9 | import java.io.File; 10 | import java.util.List; 11 | 12 | /** 13 | * EnchantmentAPI © 2017 14 | * com.sucy.enchant.skillapi.SkillAPIHook 15 | */ 16 | public class SkillAPIHook { 17 | 18 | public static List getEnchantments(final EnchantmentAPI plugin) { 19 | if (Bukkit.getPluginManager().isPluginEnabled("SkillAPI")) { 20 | return loadAll(plugin); 21 | } 22 | plugin.getLogger().info("SkillAPI not enabled, skipping skill-based enchantments"); 23 | return ImmutableList.of(); 24 | } 25 | 26 | private static List loadAll(final EnchantmentAPI plugin) { 27 | final String path = plugin.getDataFolder().getAbsolutePath() + "/" + SkillEnchantment.SAVE_FOLDER; 28 | final File directory = new File(path); 29 | if (!directory.exists()) { 30 | if (!directory.mkdirs()) { 31 | plugin.getLogger().severe("Unable to create folder at: " + path); 32 | } 33 | } 34 | 35 | final File[] files = directory.listFiles(); 36 | if (files == null) { 37 | plugin.getLogger().warning("Folder \"" + path + "\" does not exist"); 38 | return ImmutableList.of(); 39 | } 40 | 41 | final ImmutableList.Builder result = ImmutableList.builder(); 42 | for (final File file : files) { 43 | if (!file.getName().endsWith(".yml")) { 44 | plugin.getLogger().warning(file.getName() + " is not a .yml file but is in the skill enchantments folder"); 45 | continue; 46 | } 47 | 48 | try { 49 | final String fileName = file.getName().replace(".yml", ""); 50 | final CommentedConfig config = new CommentedConfig(plugin, SkillEnchantment.SAVE_FOLDER + fileName); 51 | result.add(new SkillEnchantment(fileName, config.getConfig())); 52 | } catch (final Exception ex) { 53 | // Do nothing 54 | plugin.getLogger().warning("Failed to load " + file.getName() + ", make sure it points to a valid skill"); 55 | ex.printStackTrace(); 56 | } 57 | } 58 | return result.build(); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/com/sucy/enchant/skillapi/SkillEnchantment.java: -------------------------------------------------------------------------------- 1 | package com.sucy.enchant.skillapi; 2 | 3 | import com.rit.sucy.config.parse.DataSection; 4 | import com.sucy.enchant.api.Cooldowns; 5 | import com.sucy.enchant.api.CustomEnchantment; 6 | import com.sucy.skill.SkillAPI; 7 | import com.sucy.skill.api.skills.PassiveSkill; 8 | import com.sucy.skill.api.skills.Skill; 9 | import com.sucy.skill.api.skills.SkillAttribute; 10 | import com.sucy.skill.api.skills.SkillShot; 11 | import com.sucy.skill.api.skills.TargetSkill; 12 | import org.bukkit.entity.LivingEntity; 13 | import org.bukkit.entity.Player; 14 | import org.bukkit.event.block.Action; 15 | import org.bukkit.event.player.PlayerInteractEntityEvent; 16 | import org.bukkit.event.player.PlayerInteractEvent; 17 | 18 | /** 19 | * EnchantmentAPI © 2017 20 | * com.sucy.enchant.skillapi.SkillEnchantment 21 | */ 22 | public class SkillEnchantment extends CustomEnchantment { 23 | 24 | private static final String SKILL = "skill"; 25 | private static final String EFFECT = "effect."; 26 | private static final String CLICK = "right-click"; 27 | 28 | private final Skill skill; 29 | 30 | SkillEnchantment(final String key, final DataSection data) { 31 | super(key, "No description provided"); 32 | 33 | String skillName = data.getString(SKILL, data.getString(EFFECT + SKILL)); 34 | settings.set(SKILL, skillName); 35 | settings.set(CLICK, true); 36 | 37 | skill = SkillAPI.getSkill(skillName); 38 | if (skill == null) System.out.println(data.getString(SKILL) + " is not a skill"); 39 | setMaxLevel(skill.getMaxLevel()); 40 | Cooldowns.configure(settings, 41 | skill.getSettings().getBase(SkillAttribute.COOLDOWN), 42 | skill.getSettings().getScale(SkillAttribute.COOLDOWN)); 43 | } 44 | 45 | @Override 46 | public void applyEquip(final LivingEntity user, final int level) { 47 | if (skill instanceof PassiveSkill) { 48 | ((PassiveSkill) skill).initialize(user, level); 49 | } 50 | } 51 | 52 | @Override 53 | public void applyUnequip(final LivingEntity user, final int level) { 54 | if (skill instanceof PassiveSkill) { 55 | ((PassiveSkill) skill).stopEffects(user, level); 56 | } 57 | } 58 | 59 | @Override 60 | public void applyInteractBlock( 61 | final Player user, final int level, final PlayerInteractEvent event) { 62 | 63 | if (event.getAction() == Action.PHYSICAL) return; 64 | final boolean isRightClick = event.getAction() == Action.RIGHT_CLICK_AIR 65 | || event.getAction() == Action.RIGHT_CLICK_BLOCK; 66 | 67 | if (isRightClick == settings.getBoolean(CLICK, true)) { 68 | applySkillShot(user, level); 69 | } 70 | } 71 | 72 | @Override 73 | public void applyInteractEntity( 74 | final Player user, final int level, final PlayerInteractEntityEvent event) { 75 | if (skill instanceof TargetSkill && event.getRightClicked() instanceof LivingEntity) { 76 | if (!Cooldowns.onCooldown(this, user, settings, level)) { 77 | final LivingEntity target = (LivingEntity)event.getRightClicked(); 78 | if (((TargetSkill) skill).cast(user, target, level, SkillAPI.getSettings().isAlly(user, target))) { 79 | Cooldowns.start(this, user); 80 | } 81 | } 82 | } else applySkillShot(user, level); 83 | } 84 | 85 | private void applySkillShot(final Player user, final int level) { 86 | if (skill instanceof SkillShot && !Cooldowns.onCooldown(this, user, settings, level)) { 87 | if (((SkillShot) skill).cast(user, level)) { 88 | Cooldowns.start(this, user); 89 | } 90 | } 91 | } 92 | 93 | static final String SAVE_FOLDER = "enchant/skill/"; 94 | 95 | @Override 96 | public String getSaveFolder() { 97 | return SAVE_FOLDER; 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/com/sucy/enchant/util/LoreReader.java: -------------------------------------------------------------------------------- 1 | package com.sucy.enchant.util; 2 | 3 | import com.sucy.enchant.EnchantmentAPI; 4 | import com.sucy.enchant.api.CustomEnchantment; 5 | import org.bukkit.ChatColor; 6 | 7 | import java.util.Objects; 8 | 9 | /** 10 | * EnchantmentAPI © 2017 11 | * com.sucy.enchant.util.LoreReader 12 | */ 13 | public class LoreReader { 14 | 15 | /** 16 | * Parses an enchantment name from a bit of text, assuming the format 17 | * is "{color}{enchantment} {level}" 18 | * 19 | * @param text text to parse from 20 | * @return enchantment name 21 | */ 22 | public static String parseEnchantmentName(final String text) { 23 | Objects.requireNonNull(text, "Text cannot be null"); 24 | if (!text.startsWith(ChatColor.GRAY.toString())) return ""; 25 | 26 | final String noColor = text.substring(2); 27 | final int index = noColor.lastIndexOf(' '); 28 | final int level = RomanNumerals.fromNumerals(noColor.substring(index + 1)); 29 | return level > 0 ? noColor.substring(0, index) 30 | : noColor; 31 | } 32 | 33 | /** 34 | * Parses a level from a lore line. This expects valid roman numerals to be at the end of the line 35 | * with no spaces after it, matching the format for enchantments. 36 | * 37 | * @param text text to parse the level from 38 | * @return parsed level or 1 if not found 39 | */ 40 | public static int parseEnchantmentLevel(final String text) { 41 | Objects.requireNonNull(text, "Text cannot be null"); 42 | 43 | final int index = text.lastIndexOf(' '); 44 | final int level = RomanNumerals.fromNumerals(text.substring(index + 1)); 45 | return level > 0 ? level : 1; 46 | } 47 | 48 | /** 49 | * Formats an enchantment name for appending to an item's lore. 50 | * 51 | * @param enchantment enchantment name 52 | * @param level level of the enchantment 53 | * @return lore string for the enchantment 54 | */ 55 | public static String formatEnchantment(final CustomEnchantment enchantment, final int level) { 56 | return ChatColor.GRAY + enchantment.getName() + (enchantment.getMaxLevel() > 1 ? " " + RomanNumerals.toNumerals(level) : ""); 57 | } 58 | 59 | /** 60 | * Checks whether or not the lore line is the line for the given enchantment 61 | * 62 | * @param line line to check 63 | * @return true if the line matches, false otherwise 64 | */ 65 | public static boolean isEnchantment(final String line) { 66 | return EnchantmentAPI.isRegistered(parseEnchantmentName(line)); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/com/sucy/enchant/util/RomanNumerals.java: -------------------------------------------------------------------------------- 1 | package com.sucy.enchant.util; 2 | 3 | import com.google.common.collect.ImmutableMap; 4 | import org.apache.commons.lang.Validate; 5 | 6 | import java.util.Arrays; 7 | import java.util.Map; 8 | 9 | /** 10 | * EnchantmentAPI © 2017 11 | * com.sucy.enchant.util.RomanNumerals 12 | */ 13 | public class RomanNumerals { 14 | 15 | /** 16 | * Supported roman numeral characters 17 | */ 18 | private enum RomanNumber { 19 | //It is important that the order is in reverse 20 | M(1000), CM(900), D(500), CD(400), C(100), XC(90), L(50), XL(40), X(10), IX(9), V(5), IV(4), I(1); 21 | 22 | private final int value; 23 | 24 | RomanNumber(final int value) { 25 | this.value = value; 26 | } 27 | 28 | public int getValue() { 29 | return value; 30 | } 31 | } 32 | 33 | /** 34 | * Gets the Roman Numeral string representing the given value 35 | * 36 | * @param value value to be converted 37 | * @return Roman Numeral String 38 | */ 39 | public static String toNumerals(int value) { 40 | Validate.isTrue(value > 0, "Roman numbers can't express zero or negative numbers"); 41 | final StringBuilder builder = new StringBuilder(); 42 | for (final RomanNumber romanNumber : RomanNumber.values()) { 43 | while (value >= romanNumber.getValue()) { 44 | value -= romanNumber.getValue(); 45 | builder.append(romanNumber.name()); 46 | } 47 | } 48 | return builder.toString(); 49 | } 50 | 51 | /** 52 | * Parses a Roman Numeral string into an integer 53 | * 54 | * @param romanNumeral Roman Numeral string to parse 55 | * @return integer value (0 if invalid string) 56 | */ 57 | public static int fromNumerals(final String romanNumeral) { 58 | if (romanNumeral == null || romanNumeral.isEmpty()) return 0; 59 | 60 | int result = 0; 61 | int next = getNumeralValue(romanNumeral.charAt(0)); 62 | for (int i = 1; i < romanNumeral.length(); i++) { 63 | final int value = next; 64 | next = getNumeralValue(romanNumeral.charAt(i)); 65 | // Invalid characters hint that it isn't a roman numeral string 66 | if (next == 0) return 0; 67 | result += next > value ? -value : value; 68 | } 69 | return result + next; 70 | } 71 | 72 | /** 73 | * Gets the value of the given Roman Numeral character 74 | * 75 | * @param numeral Roman Numeral character 76 | * @return value of the character 77 | */ 78 | private static int getNumeralValue(char numeral) { 79 | return CHAR_TO_NUMBER.getOrDefault(numeral, 0); 80 | } 81 | 82 | private static final Map CHAR_TO_NUMBER = Arrays.stream(RomanNumber.values()) 83 | .filter(num -> num.name().length() == 1) 84 | .collect(ImmutableMap.toImmutableMap(num -> num.name().charAt(0), RomanNumber::getValue)); 85 | } 86 | -------------------------------------------------------------------------------- /src/com/sucy/enchant/util/Utils.java: -------------------------------------------------------------------------------- 1 | package com.sucy.enchant.util; 2 | 3 | import org.bukkit.Material; 4 | import org.bukkit.inventory.ItemStack; 5 | 6 | /** 7 | * EnchantmentAPI © 2017 8 | * com.sucy.enchant.util.Utils 9 | */ 10 | public class Utils { 11 | 12 | /** 13 | * @param item item to check 14 | * @return true if isPresent, false otherwise 15 | */ 16 | public static boolean isPresent(final ItemStack item) { 17 | return item != null && item.getType() != Material.AIR; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/com/sucy/enchant/vanilla/Vanilla.java: -------------------------------------------------------------------------------- 1 | package com.sucy.enchant.vanilla; 2 | 3 | import com.sucy.enchant.EnchantmentAPI; 4 | import com.sucy.enchant.api.CustomEnchantment; 5 | 6 | import java.util.HashMap; 7 | import java.util.Map; 8 | import java.util.Optional; 9 | 10 | /** 11 | * EnchantmentAPI © 2017 12 | * com.sucy.enchant.vanilla.Vanilla 13 | */ 14 | public class Vanilla { 15 | 16 | public static final Optional getEnchantment(final String name) { 17 | final String conversion = ENCHANTS.getOrDefault(name.toLowerCase(), name).replace(" ", "_"); 18 | return Optional.ofNullable(EnchantmentAPI.getEnchantment(conversion)); 19 | } 20 | 21 | private static final Map ENCHANTS = new HashMap() {{ 22 | put("knockback", "KNOCKBACK"); 23 | put("looting", "LOOT_BONUS_MOBS"); 24 | put("sharpness", "DAMAGE_ALL"); 25 | put("smite", "DAMAGE_UNDEAD"); 26 | put("bane of arthropods", "DAMAGE_ARTHROPODS"); 27 | put("fire aspect", "FIRE_ASPECT"); 28 | put("infinity", "ARROW_INFINITE"); 29 | put("flame", "ARROW_FIRE"); 30 | put("punch", "ARROW_KNOCKBACK"); 31 | put("power", "ARROW_DAMAGE"); 32 | put("respiration", "OXYGEN"); 33 | put("aqua affinity", "WATER_WORKER"); 34 | put("feather falling", "PROTECTION_FALL"); 35 | put("thorns", "THORNS"); 36 | put("protection", "PROTECTION_ENVIRONMENTAL"); 37 | put("fire protection", "PROTECTION_FIRE"); 38 | put("blast protection", "PROTECTION_EXPLOSIONS"); 39 | put("projectile protection", "PROTECTION_PROJECTILE"); 40 | put("efficiency", "DIG_SPEED"); 41 | put("unbreaking", "DURABILITY"); 42 | put("fortune", "LOOT_BONUS_BLOCKS"); 43 | put("silk touch", "SILK_TOUCH"); 44 | }}; 45 | } 46 | -------------------------------------------------------------------------------- /src/com/sucy/enchant/vanilla/VanillaData.java: -------------------------------------------------------------------------------- 1 | package com.sucy.enchant.vanilla; 2 | 3 | import com.sucy.enchant.api.ItemSet; 4 | import org.bukkit.enchantments.Enchantment; 5 | 6 | import static com.sucy.enchant.api.CustomEnchantment.DEFAULT_GROUP; 7 | import static com.sucy.enchant.api.ItemSet.ALL; 8 | import static com.sucy.enchant.api.ItemSet.ARMOR; 9 | import static com.sucy.enchant.api.ItemSet.AXES; 10 | import static com.sucy.enchant.api.ItemSet.BOOTS; 11 | import static com.sucy.enchant.api.ItemSet.BOWS; 12 | import static com.sucy.enchant.api.ItemSet.CHESTPLATES; 13 | import static com.sucy.enchant.api.ItemSet.DURABILITY_ALL; 14 | import static com.sucy.enchant.api.ItemSet.DURABILITY_SECONDARY; 15 | import static com.sucy.enchant.api.ItemSet.FISHING; 16 | import static com.sucy.enchant.api.ItemSet.HELMETS; 17 | import static com.sucy.enchant.api.ItemSet.NONE; 18 | import static com.sucy.enchant.api.ItemSet.SHEARS; 19 | import static com.sucy.enchant.api.ItemSet.SWORDS; 20 | import static com.sucy.enchant.api.ItemSet.TOOLS; 21 | import static com.sucy.enchant.api.ItemSet.TRIDENT; 22 | import static com.sucy.enchant.api.ItemSet.WEAPONS; 23 | 24 | /** 25 | * EnchantmentAPI © 2017 26 | * com.sucy.enchant.vanilla.VanillaData 27 | */ 28 | public enum VanillaData { 29 | 30 | // ---- Armor ---- // 31 | BINDING_CURSE(ALL, 1, 1, 25, 0, 50, 8, false), 32 | DEPTH_STRIDER(BOOTS, NONE, 3, 2, 10, 10, 5, 4, true, "stride"), 33 | FROST_WALKER(BOOTS, NONE, 2, 2, 10, 10, 5, 4, false, "stride"), 34 | OXYGEN(HELMETS, 3, 2, 10, 10, 20, 4, true), 35 | PROTECTION_ENVIRONMENTAL(ARMOR, NONE, 4, 10, 1, 11, 0, 1, true, "protection"), 36 | PROTECTION_EXPLOSIONS(ARMOR, NONE, 4, 2, 5, 8, 0, 4, true, "protection"), 37 | PROTECTION_FALL(BOOTS, NONE, 4, 5, 5, 6, 0, 2, true, "protection"), 38 | PROTECTION_FIRE(ARMOR, NONE, 4, 5, 10, 8, 0, 2, true, "protection"), 39 | PROTECTION_PROJECTILE(ARMOR, NONE, 4, 5, 3, 6, 0, 2, true, "protection"), 40 | THORNS(CHESTPLATES, ARMOR, 3, 1, -10, 20, 30, 8, true, DEFAULT_GROUP), 41 | WATER_WORKER(HELMETS, 1, 2, 1, 0, 40, 4, true), 42 | 43 | // ---- Swords ---- // 44 | DAMAGE_ALL(WEAPONS, AXES, 5, 10, 1, 11, 9, 1, true, "damage"), 45 | DAMAGE_ARTHROPODS(WEAPONS, AXES, 5, 5, 5, 8, 12, 2, true, "damage"), 46 | DAMAGE_UNDEAD(WEAPONS, AXES, 5, 5, 5, 8, 12, 2, true, "damage"), 47 | FIRE_ASPECT(SWORDS, 2, 2, 10, 20, 30, 4, true), 48 | KNOCKBACK(SWORDS, 2, 5, 5, 20, 30, 2, true), 49 | LOOT_BONUS_MOBS(SWORDS, 3, 2, 15, 9, 41, 4, true), 50 | SWEEPING_EDGE(SWORDS, 3, 1, 5, 9, 6, 4, true), 51 | 52 | // --- Tools --- // 53 | DIG_SPEED(TOOLS, SHEARS, 5, 10, 1, 10, 40, 1, true, DEFAULT_GROUP), 54 | LOOT_BONUS_BLOCKS(TOOLS, NONE, 3, 2, 15, 9, 41, 4, true, "blocks"), 55 | SILK_TOUCH(TOOLS, NONE, 1, 1, 15, 0, 50, 8, true, "blocks"), 56 | 57 | // ---- Bows ---- // 58 | ARROW_DAMAGE(BOWS, 5, 10, 1, 10, 5, 1, true), 59 | ARROW_FIRE(BOWS, 1, 2, 20, 0, 50, 4, true), 60 | ARROW_INFINITE(BOWS, NONE, 1, 1, 20, 0, 50, 8, true, "infinite"), 61 | ARROW_KNOCKBACK(BOWS, 2, 2, 12, 20, 5, 4, true), 62 | 63 | // ---- Fishing ---- // 64 | LUCK(FISHING, 3, 2, 15, 9, 41, 4, true), 65 | LURE(FISHING, 3, 2, 15, 9, 41, 4, true), 66 | 67 | // ---- Trident ---- // 68 | LOYALTY(TRIDENT, 3, 5, 5, 7, 43, 2, true), 69 | IMPALING(TRIDENT, 5, 2, 1, 8, 12, 4, true), 70 | RIPTIDE(TRIDENT, 3, 2, 10, 7, 43, 4, true), 71 | CHANNELING(TRIDENT, 1, 1, 25, 0, 50, 8, true), 72 | 73 | // ---- All ---- // 74 | DURABILITY(ItemSet.DURABILITY, DURABILITY_SECONDARY, 3, 5, 5, 8, 42, 2, true, DEFAULT_GROUP), 75 | MENDING(DURABILITY_ALL, NONE, 1, 2, 0, 25, 25, 4, false, "infinite"), 76 | VANISHING_CURSE(ALL, 1, 1, 25, 0, 50, 8, false); 77 | 78 | private VanillaEnchantment enchantment; 79 | 80 | public VanillaEnchantment getEnchantment() { 81 | return enchantment; 82 | } 83 | 84 | public boolean doesExist() { 85 | return enchantment != null; 86 | } 87 | 88 | VanillaData( 89 | final ItemSet itemSet, 90 | final int maxLevel, 91 | final int weight, 92 | final int minEnchantLevel, 93 | final int enchantLevelScale, 94 | final int maxBuffer, 95 | final int costPerLevel, 96 | final boolean tableEnabled) { 97 | this(itemSet, NONE, maxLevel, weight, minEnchantLevel, enchantLevelScale, maxBuffer, costPerLevel, tableEnabled, DEFAULT_GROUP); 98 | } 99 | 100 | VanillaData( 101 | final ItemSet itemSet, 102 | final ItemSet secondary, 103 | final int maxLevel, 104 | final int weight, 105 | final int minEnchantLevel, 106 | final int enchantLevelScale, 107 | final int maxBuffer, 108 | final int costPerLevel, 109 | final boolean tableEnabled, 110 | final String group) { 111 | 112 | final Enchantment enchant = Enchantment.getByName(name()); 113 | if (enchant == null) return; 114 | 115 | enchantment = new VanillaEnchantment(enchant); 116 | enchantment.addNaturalItems(itemSet.getItems()); 117 | enchantment.addAnvilItems(secondary.getItems()); 118 | enchantment.setMaxLevel(maxLevel); 119 | enchantment.setWeight(weight); 120 | enchantment.setMinEnchantingLevel(minEnchantLevel); 121 | enchantment.setEnchantLevelScaleFactor(enchantLevelScale); 122 | enchantment.setEnchantLevelBuffer(maxBuffer); 123 | enchantment.setCombineCostPerLevel(costPerLevel); 124 | enchantment.setTableEnabled(tableEnabled); 125 | enchantment.setGroup(group); 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /src/com/sucy/enchant/vanilla/VanillaEnchantment.java: -------------------------------------------------------------------------------- 1 | package com.sucy.enchant.vanilla; 2 | 3 | import com.sucy.enchant.api.CustomEnchantment; 4 | import com.sucy.enchant.data.Permission; 5 | import org.bukkit.Material; 6 | import org.bukkit.enchantments.Enchantment; 7 | import org.bukkit.inventory.ItemStack; 8 | import org.bukkit.inventory.meta.EnchantmentStorageMeta; 9 | import org.bukkit.permissions.Permissible; 10 | 11 | import java.util.Map; 12 | 13 | /** 14 | * EnchantmentAPI © 2017 15 | * com.sucy.enchant.vanilla.VanillaEnchantment 16 | */ 17 | public class VanillaEnchantment extends CustomEnchantment { 18 | 19 | private final Enchantment enchantment; 20 | 21 | VanillaEnchantment(final Enchantment vanilla) { 22 | super(vanilla.getName(), vanilla.getName() + " vanilla enchantment"); 23 | this.enchantment = vanilla; 24 | } 25 | 26 | public Enchantment getEnchantment() { 27 | return enchantment; 28 | } 29 | 30 | @Override 31 | public ItemStack addToItem(final ItemStack item, final int level) { 32 | if (item.getType() == Material.BOOK) { 33 | item.setType(Material.ENCHANTED_BOOK); 34 | } 35 | if (item.getType() == Material.ENCHANTED_BOOK) { 36 | final EnchantmentStorageMeta meta = (EnchantmentStorageMeta)item.getItemMeta(); 37 | meta.addStoredEnchant(enchantment, level, true); 38 | item.setItemMeta(meta); 39 | return item; 40 | } 41 | item.addUnsafeEnchantment(enchantment, level); 42 | return item; 43 | } 44 | 45 | @Override 46 | public void addToEnchantment(final Map enchantments, final ItemStack result, final int level) { 47 | enchantments.put(enchantment, level); 48 | } 49 | 50 | @Override 51 | public ItemStack removeFromItem(final ItemStack item) { 52 | if (item.getType() == Material.ENCHANTED_BOOK) { 53 | final EnchantmentStorageMeta meta = (EnchantmentStorageMeta)item.getItemMeta(); 54 | meta.removeStoredEnchant(enchantment); 55 | item.setItemMeta(meta); 56 | } 57 | item.removeEnchantment(enchantment); 58 | return item; 59 | } 60 | 61 | @Override 62 | public boolean hasPermission(final Permissible permissible) { 63 | return permissible == null 64 | || permissible.hasPermission(Permission.ENCHANT_VANILLA) 65 | || permissible.hasPermission(getPermission()); 66 | } 67 | 68 | @Override 69 | public String getSaveFolder() { 70 | return "enchant/vanilla/"; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /tst/com/sucy/enchant/TestUtils.java: -------------------------------------------------------------------------------- 1 | package com.sucy.enchant; 2 | 3 | import com.sucy.enchant.api.CustomEnchantment; 4 | import com.sucy.enchant.vanilla.VanillaEnchantment; 5 | import org.bukkit.enchantments.Enchantment; 6 | 7 | import java.lang.reflect.Field; 8 | import java.lang.reflect.Modifier; 9 | import java.util.Map; 10 | 11 | import static org.mockito.Matchers.anyVararg; 12 | import static org.mockito.Mockito.doCallRealMethod; 13 | import static org.mockito.Mockito.mock; 14 | import static org.mockito.Mockito.when; 15 | 16 | /** 17 | * EnchantmentAPI © 2017 18 | * com.sucy.enchant.TestUtils 19 | */ 20 | public class TestUtils { 21 | 22 | public static final EnchantmentAPI API = wrapAPI(); 23 | 24 | public static Enchantment createVanillaEnchantment(final String name) { 25 | final VanillaEnchantment enchant = mock(VanillaEnchantment.class); 26 | when(enchant.getName()).thenReturn(name); 27 | API.register(enchant); 28 | 29 | final Enchantment enchantment = mock(Enchantment.class); 30 | when(enchantment.getName()).thenReturn(name); 31 | return enchantment; 32 | } 33 | 34 | public static CustomEnchantment createEnchantment(final String name) { 35 | final CustomEnchantment enchant = mock(CustomEnchantment.class); 36 | when(enchant.getName()).thenReturn(name); 37 | API.register(enchant); 38 | return enchant; 39 | } 40 | 41 | public static void tearDown() throws Exception { 42 | getRegistry().clear(); 43 | } 44 | 45 | @SuppressWarnings("unchecked") 46 | public static Map getRegistry() throws Exception { 47 | return ((Map)getField(EnchantmentAPI.class, "ENCHANTMENTS").get(null)); 48 | } 49 | 50 | public static void register(final CustomEnchantment enchantment) throws Exception { 51 | getRegistry().put(enchantment.getName(), enchantment); 52 | } 53 | 54 | public static void set(final Class clazz, final String fieldName, final Object value) throws Exception { 55 | getField(clazz, fieldName).set(null, value); 56 | } 57 | 58 | public static Field getField(final Class clazz, final String name) throws Exception { 59 | final Field field = clazz.getDeclaredField(name); 60 | field.setAccessible(true); 61 | 62 | final Field modifier = Field.class.getDeclaredField("modifiers"); 63 | modifier.setAccessible(true); 64 | modifier.setInt(field, field.getModifiers() & ~Modifier.FINAL); 65 | 66 | return field; 67 | } 68 | 69 | private static EnchantmentAPI wrapAPI() { 70 | final EnchantmentAPI api = mock(EnchantmentAPI.class); 71 | doCallRealMethod().when(api).register(anyVararg()); 72 | return api; 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /tst/com/sucy/enchant/api/CooldownsTest.java: -------------------------------------------------------------------------------- 1 | package com.sucy.enchant.api; 2 | 3 | import com.sucy.enchant.TestUtils; 4 | import org.bukkit.entity.LivingEntity; 5 | import org.junit.After; 6 | import org.junit.Before; 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | import org.mockito.Mock; 10 | import org.mockito.runners.MockitoJUnitRunner; 11 | 12 | import java.time.Clock; 13 | import java.util.UUID; 14 | 15 | import static org.junit.Assert.assertEquals; 16 | import static org.junit.Assert.assertFalse; 17 | import static org.junit.Assert.assertTrue; 18 | import static org.mockito.Mockito.verify; 19 | import static org.mockito.Mockito.when; 20 | 21 | /** 22 | * EnchantmentAPI © 2017 23 | * com.sucy.enchant.api.CooldownsTest 24 | */ 25 | @RunWith(MockitoJUnitRunner.class) 26 | public class CooldownsTest { 27 | 28 | private static final String ENCHANT_NAME = "enchantName"; 29 | private static final UUID USER_ID = UUID.randomUUID(); 30 | 31 | @Mock 32 | private Clock clock; 33 | @Mock 34 | private CustomEnchantment enchantment; 35 | @Mock 36 | private LivingEntity user; 37 | @Mock 38 | private Settings settings; 39 | 40 | @Before 41 | public void setUp() throws Exception { 42 | TestUtils.set(Cooldowns.class, "clock", clock); 43 | 44 | when(enchantment.getName()).thenReturn(ENCHANT_NAME); 45 | when(user.getUniqueId()).thenReturn(USER_ID); 46 | } 47 | 48 | @After 49 | public void tearDown() throws Exception { 50 | } 51 | 52 | @Test 53 | public void configure() throws Exception { 54 | Cooldowns.configure(settings, 12, 23); 55 | 56 | verify(settings).set("cooldown", 12, 23); 57 | } 58 | 59 | @Test 60 | public void secondsLeft() throws Exception { 61 | when(clock.millis()).thenReturn(100L, 1100L); 62 | when(settings.get("cooldown", 2)).thenReturn(15.0); 63 | 64 | Cooldowns.start(enchantment, user); 65 | final int result = Cooldowns.secondsLeft(enchantment, user, settings, 2); 66 | assertEquals(result, 14); 67 | } 68 | 69 | @Test 70 | public void secondsLeft_roundedUp() throws Exception { 71 | when(clock.millis()).thenReturn(100L, 1000L); 72 | when(settings.get("cooldown", 2)).thenReturn(1.0); 73 | 74 | Cooldowns.start(enchantment, user); 75 | final int result = Cooldowns.secondsLeft(enchantment, user, settings, 2); 76 | assertEquals(result, 1); 77 | } 78 | 79 | @Test 80 | public void onCooldown() throws Exception { 81 | when(clock.millis()).thenReturn(100L, 1100L); 82 | when(settings.get("cooldown", 2)).thenReturn(15.0); 83 | 84 | Cooldowns.start(enchantment, user); 85 | final boolean result = Cooldowns.onCooldown(enchantment, user, settings, 2); 86 | assertTrue(result); 87 | } 88 | 89 | @Test 90 | public void onCooldown_exact() throws Exception { 91 | when(clock.millis()).thenReturn(100L, 15100L); 92 | when(settings.get("cooldown", 2)).thenReturn(15.0); 93 | 94 | Cooldowns.start(enchantment, user); 95 | final boolean result = Cooldowns.onCooldown(enchantment, user, settings, 2); 96 | assertFalse(result); 97 | } 98 | 99 | @Test 100 | public void onCooldown_offCooldown() throws Exception { 101 | when(clock.millis()).thenReturn(100L, 20100L); 102 | when(settings.get("cooldown", 2)).thenReturn(15.0); 103 | 104 | Cooldowns.start(enchantment, user); 105 | final boolean result = Cooldowns.onCooldown(enchantment, user, settings, 2); 106 | assertFalse(result); 107 | } 108 | 109 | @Test 110 | public void reduce() throws Exception { 111 | when(clock.millis()).thenReturn(100L, 10100L); 112 | when(settings.get("cooldown", 2)).thenReturn(15.0); 113 | 114 | Cooldowns.start(enchantment, user); 115 | Cooldowns.reduce(enchantment, user, 6000); 116 | final boolean result = Cooldowns.onCooldown(enchantment, user, settings, 2); 117 | assertFalse(result); 118 | } 119 | 120 | @Test 121 | public void reduceNegative() throws Exception { 122 | when(clock.millis()).thenReturn(100L, 20100L); 123 | when(settings.get("cooldown", 2)).thenReturn(15.0); 124 | 125 | Cooldowns.start(enchantment, user); 126 | Cooldowns.reduce(enchantment, user, -6000); 127 | final boolean result = Cooldowns.onCooldown(enchantment, user, settings, 2); 128 | assertTrue(result); 129 | } 130 | } -------------------------------------------------------------------------------- /tst/com/sucy/enchant/api/CustomEnchantmentTest.java: -------------------------------------------------------------------------------- 1 | package com.sucy.enchant.api; 2 | 3 | import com.google.common.collect.ImmutableList; 4 | import com.sucy.enchant.TestUtils; 5 | import org.bukkit.ChatColor; 6 | import org.bukkit.Material; 7 | import org.bukkit.inventory.ItemStack; 8 | import org.bukkit.inventory.meta.ItemMeta; 9 | import org.junit.After; 10 | import org.junit.Before; 11 | import org.junit.Test; 12 | import org.junit.runner.RunWith; 13 | import org.mockito.Mock; 14 | import org.mockito.runners.MockitoJUnitRunner; 15 | 16 | import java.util.ArrayList; 17 | import java.util.List; 18 | 19 | import static org.junit.Assert.assertEquals; 20 | import static org.junit.Assert.assertFalse; 21 | import static org.junit.Assert.assertTrue; 22 | import static org.mockito.Mockito.verify; 23 | import static org.mockito.Mockito.when; 24 | 25 | /** 26 | * EnchantmentAPI © 2017 27 | * com.sucy.enchant.api.CustomEnchantmentTest 28 | */ 29 | @RunWith(MockitoJUnitRunner.class) 30 | public class CustomEnchantmentTest { 31 | 32 | @Mock 33 | private ItemMeta meta; 34 | @Mock 35 | private ItemStack item; 36 | 37 | private List lore; 38 | 39 | private CustomEnchantment subject; 40 | 41 | @Before 42 | public void setUp() throws Exception { 43 | subject = new TestEnchant(); 44 | TestUtils.register(subject); 45 | 46 | lore = new ArrayList<>(); 47 | when(item.getType()).thenReturn(Material.DIAMOND_SWORD); 48 | when(item.getItemMeta()).thenReturn(meta); 49 | when(meta.getLore()).thenReturn(lore); 50 | when(meta.hasLore()).thenReturn(true); 51 | } 52 | 53 | @After 54 | public void tearDown() throws Exception { 55 | } 56 | 57 | @Test 58 | public void computeLevel() throws Exception { 59 | // Use data from ARROW_DAMAGE to have a solid baseline to compare against 60 | subject.setMaxLevel(10, 5); 61 | subject.setMinEnchantingLevel(1); 62 | subject.setEnchantLevelScaleFactor(10); 63 | subject.setEnchantLevelBuffer(5); 64 | 65 | assertEquals(0, subject.computeLevel(0)); 66 | assertEquals(1, subject.computeLevel(1)); 67 | assertEquals(1, subject.computeLevel(10)); 68 | assertEquals(2, subject.computeLevel(11)); 69 | assertEquals(2, subject.computeLevel(20)); 70 | assertEquals(3, subject.computeLevel(21)); 71 | assertEquals(3, subject.computeLevel(30)); 72 | assertEquals(4, subject.computeLevel(31)); 73 | assertEquals(4, subject.computeLevel(40)); 74 | assertEquals(5, subject.computeLevel(41)); 75 | assertEquals(5, subject.computeLevel(50)); 76 | assertEquals(5, subject.computeLevel(56)); 77 | assertEquals(0, subject.computeLevel(57)); 78 | } 79 | 80 | @Test 81 | public void canEnchantOnto() throws Exception { 82 | subject.addNaturalItems(ItemSet.AXES.getItems()); 83 | 84 | assertTrue(subject.canEnchantOnto(new ItemStack(Material.DIAMOND_AXE))); 85 | assertTrue(subject.canEnchantOnto(new ItemStack(Material.STONE_AXE))); 86 | assertTrue(subject.canEnchantOnto(new ItemStack(Material.BOOK))); 87 | assertTrue(subject.canEnchantOnto(new ItemStack(Material.ENCHANTED_BOOK))); 88 | assertFalse(subject.canEnchantOnto(new ItemStack(Material.DIAMOND_SWORD))); 89 | assertFalse(subject.canEnchantOnto(new ItemStack(Material.LAPIS_BLOCK))); 90 | assertFalse(subject.canEnchantOnto(new ItemStack(Material.BOW))); 91 | assertFalse(subject.canEnchantOnto(new ItemStack(Material.STONE_PICKAXE))); 92 | } 93 | 94 | @Test 95 | public void conflictsWith_default() throws Exception { 96 | final TestEnchant other = new TestEnchant(); 97 | assertFalse(subject.conflictsWith(other, true)); 98 | assertFalse(subject.conflictsWith(other, false)); 99 | } 100 | 101 | @Test 102 | public void conflictsWith_same() throws Exception { 103 | assertTrue(subject.conflictsWith(subject, true)); 104 | assertFalse(subject.conflictsWith(subject, false)); 105 | } 106 | 107 | @Test 108 | public void conflictsWith_differentGroups() throws Exception { 109 | final TestEnchant other = new TestEnchant(); 110 | subject.setGroup("subject"); 111 | other.setGroup("other"); 112 | 113 | assertFalse(subject.conflictsWith(other, true)); 114 | assertFalse(subject.conflictsWith(other, false)); 115 | } 116 | 117 | @Test 118 | public void conflictsWith_sameGroups() throws Exception { 119 | final TestEnchant other = new TestEnchant(); 120 | subject.setGroup("same"); 121 | other.setGroup("same"); 122 | 123 | assertTrue(subject.conflictsWith(other, true)); 124 | assertTrue(subject.conflictsWith(other, false)); 125 | } 126 | 127 | @Test 128 | public void conflictsWith_list() throws Exception { 129 | assertTrue(subject.conflictsWith(ImmutableList.of(subject), true)); 130 | assertFalse(subject.conflictsWith(ImmutableList.of(), true)); 131 | } 132 | 133 | @Test 134 | public void conflictsWith_array() throws Exception { 135 | assertTrue(subject.conflictsWith(true, subject)); 136 | assertFalse(subject.conflictsWith(true)); 137 | } 138 | 139 | @Test 140 | public void addToItem_happyCase() throws Exception { 141 | subject.addToItem(item, 2); 142 | assertEquals(1, lore.size()); 143 | assertEquals(ChatColor.GRAY + "test II", lore.get(0)); 144 | verify(meta).setLore(lore); 145 | verify(item).setItemMeta(meta); 146 | } 147 | 148 | @Test 149 | public void addToItem_toTop() throws Exception { 150 | lore.add("Line"); 151 | lore.add("Another Line"); 152 | subject.addToItem(item, 2); 153 | assertEquals(3, lore.size()); 154 | assertEquals(ChatColor.GRAY + "test II", lore.get(0)); 155 | verify(meta).setLore(lore); 156 | verify(item).setItemMeta(meta); 157 | } 158 | 159 | @Test 160 | public void addToItem_alreadyExists() throws Exception { 161 | lore.add(ChatColor.GRAY + "test IV"); 162 | lore.add("junk"); 163 | lore.add("Another Line"); 164 | subject.addToItem(item, 2); 165 | assertEquals(3, lore.size()); 166 | assertEquals(ChatColor.GRAY + "test II", lore.get(0)); 167 | assertEquals("junk", lore.get(1)); 168 | assertEquals("Another Line", lore.get(2)); 169 | verify(meta).setLore(lore); 170 | verify(item).setItemMeta(meta); 171 | } 172 | 173 | @Test 174 | public void addToItem_noLore() throws Exception { 175 | when(meta.hasLore()).thenReturn(false); 176 | lore.add(ChatColor.GRAY + "test IV"); 177 | lore.add("junk"); 178 | lore.add("Another Line"); 179 | subject.addToItem(item, 2); 180 | verify(meta).setLore(ImmutableList.of(ChatColor.GRAY + "test II")); 181 | verify(item).setItemMeta(meta); 182 | } 183 | 184 | @Test 185 | public void addToEnchantment() throws Exception { 186 | } 187 | 188 | @Test 189 | public void removeFromItem() throws Exception { 190 | } 191 | 192 | @Test 193 | public void hasPermission() throws Exception { 194 | } 195 | 196 | @Test 197 | public void getPermission() throws Exception { 198 | } 199 | 200 | @Test 201 | public void applyOnHit() throws Exception { 202 | } 203 | 204 | @Test 205 | public void applyDefense() throws Exception { 206 | } 207 | 208 | @Test 209 | public void applyBreak() throws Exception { 210 | } 211 | 212 | @Test 213 | public void applyEquip() throws Exception { 214 | } 215 | 216 | @Test 217 | public void applyUnequip() throws Exception { 218 | } 219 | 220 | @Test 221 | public void applyInteractBlock() throws Exception { 222 | } 223 | 224 | @Test 225 | public void applyInteractEntity() throws Exception { 226 | } 227 | 228 | @Test 229 | public void applyProjectile() throws Exception { 230 | } 231 | 232 | @Test 233 | public void equals() throws Exception { 234 | } 235 | 236 | @Test 237 | public void compareTo() throws Exception { 238 | } 239 | 240 | @Test 241 | public void getSaveFolder() throws Exception { 242 | } 243 | 244 | @Test 245 | public void save() throws Exception { 246 | } 247 | 248 | @Test 249 | public void load() throws Exception { 250 | } 251 | 252 | private static class TestEnchant extends CustomEnchantment { 253 | TestEnchant() { super("test", "description"); } 254 | } 255 | } -------------------------------------------------------------------------------- /tst/com/sucy/enchant/api/EnchantmentsTest.java: -------------------------------------------------------------------------------- 1 | package com.sucy.enchant.api; 2 | 3 | import com.google.common.collect.ImmutableMap; 4 | import com.sucy.enchant.EnchantmentAPI; 5 | import com.sucy.enchant.TestUtils; 6 | import org.bukkit.enchantments.Enchantment; 7 | import org.bukkit.inventory.ItemStack; 8 | import org.junit.After; 9 | import org.junit.Before; 10 | import org.junit.Test; 11 | 12 | import java.util.Map; 13 | import java.util.Random; 14 | 15 | import static org.mockito.Mockito.mock; 16 | import static org.mockito.Mockito.when; 17 | 18 | /** 19 | * EnchantmentAPI © 2017 20 | * com.sucy.enchant.api.EnchantmentsTest 21 | */ 22 | public class EnchantmentsTest { 23 | 24 | private static final Map ENCHANTS = ImmutableMap.of( 25 | TestUtils.createVanillaEnchantment("TEST"), 1, 26 | TestUtils.createVanillaEnchantment("SAMPLE"), 3); 27 | 28 | private ItemStack item; 29 | 30 | @Before 31 | public void setUp() throws Exception { 32 | item = mock(ItemStack.class); 33 | } 34 | 35 | @After 36 | public void tearDown() throws Exception { 37 | TestUtils.tearDown(); 38 | } 39 | 40 | @Test 41 | public void getCustomEnchantments() throws Exception { 42 | 43 | } 44 | 45 | @Test 46 | public void getAllEnchantments() throws Exception { 47 | when(item.getEnchantments()).thenReturn(ENCHANTS); 48 | final Map result = Enchantments.getAllEnchantments(item); 49 | final Map expected = ImmutableMap.of( 50 | EnchantmentAPI.getEnchantment("TEST"), 1, 51 | EnchantmentAPI.getEnchantment("SAMPLE"), 3); 52 | } 53 | 54 | @Test 55 | public void hasCustomEnchantment() throws Exception { 56 | } 57 | 58 | @Test 59 | public void removeAllEnchantments() throws Exception { 60 | } 61 | 62 | @Test 63 | public void removeCustomEnchantments() throws Exception { 64 | } 65 | 66 | @Test 67 | public void roll() { 68 | final Random random = new Random(); 69 | final int runs = 1000000; 70 | final double increment = 100.0 / runs; 71 | double chance = 0; 72 | for (int i = 0; i < 1000000; i++) { 73 | if (random.nextInt(5) == 0 || random.nextInt(4) == 0) { 74 | chance += increment; 75 | } 76 | } 77 | System.out.println("Chance: " + chance); 78 | } 79 | } -------------------------------------------------------------------------------- /tst/com/sucy/enchant/api/ItemSetTest.java: -------------------------------------------------------------------------------- 1 | package com.sucy.enchant.api; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.assertEquals; 6 | 7 | /** 8 | * EnchantmentAPI © 2018 9 | * com.sucy.enchant.api.ItemSetTest 10 | */ 11 | public class ItemSetTest { 12 | 13 | @Test 14 | public void getItems() { 15 | assertEquals(1, ItemSet.TRIDENT.getItems().length); 16 | } 17 | } -------------------------------------------------------------------------------- /tst/com/sucy/enchant/cmd/CmdGraphTest.java: -------------------------------------------------------------------------------- 1 | package com.sucy.enchant.cmd; 2 | 3 | import com.google.common.collect.ImmutableList; 4 | import com.rit.sucy.config.parse.DataSection; 5 | import com.sucy.enchant.TestUtils; 6 | import com.sucy.enchant.data.ConfigKey; 7 | import com.sucy.enchant.data.Configuration; 8 | import org.junit.Before; 9 | import org.junit.Test; 10 | 11 | import java.util.HashSet; 12 | import java.util.List; 13 | 14 | import static org.junit.Assert.assertEquals; 15 | import static org.mockito.Mockito.mock; 16 | import static org.mockito.Mockito.when; 17 | 18 | /** 19 | * EnchantmentAPI © 2017 20 | * com.sucy.enchant.cmd.CmdGraphTest 21 | */ 22 | public class CmdGraphTest { 23 | 24 | private static final List WEIGHTS = ImmutableList.of(2.0, 2.0, 4.0, 4.0, 8.0); 25 | private static final double TOTAL = WEIGHTS.stream().mapToDouble(Double::doubleValue).sum() + 4; 26 | 27 | private CmdGraph subject; 28 | private DataSection data; 29 | 30 | @Before 31 | public void setUp() throws Exception { 32 | data = mock(DataSection.class); 33 | when(data.getInt(ConfigKey.MAX_ENCHANTMENTS.getKey())).thenReturn(3); 34 | TestUtils.set(Configuration.class, "data", data); 35 | subject = new CmdGraph(); 36 | } 37 | 38 | @Test 39 | public void compute() { 40 | double one = 4.0 / TOTAL; 41 | double two = 1.0/33 + 1.0/15 + 1.0/12; 42 | 43 | assertEquals(one, subject.compute(WEIGHTS, TOTAL, 4, 0, new HashSet<>()), 0.00000001); 44 | assertEquals(two, subject.compute(WEIGHTS, TOTAL, 4, 1, new HashSet<>()), 0.00000001); 45 | 46 | double total = 0; 47 | for (int i = 0; i <= WEIGHTS.size(); i++) { 48 | total += subject.compute(WEIGHTS, TOTAL, 4, i, new HashSet<>()); 49 | } 50 | assertEquals(1, total, 0.0000000001); 51 | } 52 | } -------------------------------------------------------------------------------- /tst/com/sucy/enchant/util/LoreReaderTest.java: -------------------------------------------------------------------------------- 1 | package com.sucy.enchant.util; 2 | 3 | import com.sucy.enchant.TestUtils; 4 | import com.sucy.enchant.api.CustomEnchantment; 5 | import org.bukkit.ChatColor; 6 | import org.junit.Test; 7 | import org.junit.runner.RunWith; 8 | import org.mockito.Mock; 9 | import org.mockito.runners.MockitoJUnitRunner; 10 | 11 | import static org.junit.Assert.assertEquals; 12 | import static org.junit.Assert.assertFalse; 13 | import static org.junit.Assert.assertTrue; 14 | import static org.mockito.Mockito.when; 15 | 16 | /** 17 | * EnchantmentAPI © 2017 18 | * com.sucy.enchant.util.LoreReaderTest 19 | */ 20 | @RunWith(MockitoJUnitRunner.class) 21 | public class LoreReaderTest { 22 | 23 | @Mock 24 | private CustomEnchantment enchantment; 25 | 26 | @Test 27 | public void parseEnchantmentName() { 28 | assertEquals("Test", LoreReader.parseEnchantmentName(ChatColor.GRAY + "Test IX")); 29 | assertEquals("With Spaces", LoreReader.parseEnchantmentName(ChatColor.GRAY + "With Spaces MLCD")); 30 | assertEquals("No Numerals Here", LoreReader.parseEnchantmentName(ChatColor.GRAY + "No Numerals Here")); 31 | assertEquals("NoSpace", LoreReader.parseEnchantmentName(ChatColor.GRAY + "NoSpace")); 32 | assertEquals("A Test", LoreReader.parseEnchantmentName(ChatColor.GRAY + "A Test")); 33 | assertEquals(" FrontSpace", LoreReader.parseEnchantmentName(ChatColor.GRAY + " FrontSpace")); 34 | assertEquals("EndSpace ", LoreReader.parseEnchantmentName(ChatColor.GRAY + "EndSpace ")); 35 | assertEquals("", LoreReader.parseEnchantmentName("Test IX")); 36 | assertEquals("", LoreReader.parseEnchantmentName(ChatColor.GRAY.toString())); 37 | assertEquals("", LoreReader.parseEnchantmentName("")); 38 | } 39 | 40 | @Test 41 | public void parseEnchantmentLevel() { 42 | assertEquals(5, LoreReader.parseEnchantmentLevel("Test V")); 43 | assertEquals(9, LoreReader.parseEnchantmentLevel("With Spaces IX")); 44 | assertEquals(1, LoreReader.parseEnchantmentLevel("No Numerals")); 45 | assertEquals(1, LoreReader.parseEnchantmentLevel("A Test")); 46 | assertEquals(1, LoreReader.parseEnchantmentLevel(" FrontSpace")); 47 | assertEquals(1, LoreReader.parseEnchantmentLevel("EndSpace ")); 48 | assertEquals(1, LoreReader.parseEnchantmentLevel("")); 49 | } 50 | 51 | @Test 52 | public void formatEnchantmentWithLevel() { 53 | when(enchantment.getName()).thenReturn("Test"); 54 | when(enchantment.getMaxLevel()).thenReturn(10); 55 | assertEquals(ChatColor.GRAY + "Test IX", LoreReader.formatEnchantment(enchantment, 9)); 56 | } 57 | 58 | @Test 59 | public void formatEnchantmentWithoutLevel() { 60 | when(enchantment.getName()).thenReturn("Test"); 61 | when(enchantment.getMaxLevel()).thenReturn(1); 62 | assertEquals(ChatColor.GRAY + "Test", LoreReader.formatEnchantment(enchantment, 9)); 63 | } 64 | 65 | @Test 66 | public void isEnchantment() { 67 | TestUtils.createEnchantment("Test"); 68 | assertTrue(LoreReader.isEnchantment(ChatColor.GRAY + "Test IX")); 69 | assertTrue(LoreReader.isEnchantment(ChatColor.GRAY + "Test")); 70 | assertFalse(LoreReader.isEnchantment("Test IX")); 71 | assertFalse(LoreReader.isEnchantment(ChatColor.GRAY + "Different IX")); 72 | assertFalse(LoreReader.isEnchantment("")); 73 | assertFalse(LoreReader.isEnchantment("")); 74 | } 75 | } -------------------------------------------------------------------------------- /tst/com/sucy/enchant/util/RomanNumeralsTest.java: -------------------------------------------------------------------------------- 1 | package com.sucy.enchant.util; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.assertEquals; 6 | 7 | /** 8 | * EnchantmentAPI © 2017 9 | * com.sucy.enchant.util.RomanNumeralsTest 10 | */ 11 | public class RomanNumeralsTest { 12 | @Test 13 | public void toNumerals() throws Exception { 14 | assertEquals("I", RomanNumerals.toNumerals(1)); 15 | assertEquals("III", RomanNumerals.toNumerals(3)); 16 | assertEquals("IV", RomanNumerals.toNumerals(4)); 17 | assertEquals("V", RomanNumerals.toNumerals(5)); 18 | assertEquals("IX", RomanNumerals.toNumerals(9)); 19 | assertEquals("X", RomanNumerals.toNumerals(10)); 20 | assertEquals("XXXVII", RomanNumerals.toNumerals(37)); 21 | assertEquals("XL", RomanNumerals.toNumerals(40)); 22 | assertEquals("L", RomanNumerals.toNumerals(50)); 23 | assertEquals("XCII", RomanNumerals.toNumerals(92)); 24 | assertEquals("CIV", RomanNumerals.toNumerals(104)); 25 | assertEquals("CD", RomanNumerals.toNumerals(400)); 26 | assertEquals("DXXXI", RomanNumerals.toNumerals(531)); 27 | assertEquals("CM", RomanNumerals.toNumerals(900)); 28 | assertEquals("MCCXXXIV", RomanNumerals.toNumerals(1234)); 29 | 30 | } 31 | 32 | @Test 33 | public void fromNumerals() throws Exception { 34 | assertEquals(1, RomanNumerals.fromNumerals("I")); 35 | assertEquals(3, RomanNumerals.fromNumerals("III")); 36 | assertEquals(4, RomanNumerals.fromNumerals("IV")); 37 | assertEquals(5, RomanNumerals.fromNumerals("V")); 38 | assertEquals(9, RomanNumerals.fromNumerals("IX")); 39 | assertEquals(10, RomanNumerals.fromNumerals("X")); 40 | assertEquals(37, RomanNumerals.fromNumerals("XXXVII")); 41 | assertEquals(40, RomanNumerals.fromNumerals("XL")); 42 | assertEquals(50, RomanNumerals.fromNumerals("L")); 43 | assertEquals(92, RomanNumerals.fromNumerals("XCII")); 44 | assertEquals(104, RomanNumerals.fromNumerals("CIV")); 45 | assertEquals(400, RomanNumerals.fromNumerals("CD")); 46 | assertEquals(531, RomanNumerals.fromNumerals("DXXXI")); 47 | assertEquals(900, RomanNumerals.fromNumerals("CM")); 48 | assertEquals(1234, RomanNumerals.fromNumerals("MCCXXXIV")); 49 | } 50 | } --------------------------------------------------------------------------------