")
+ .addClass(category === "modules"
+ ? "one-column-search-results"
+ : "two-column-search-results");
+ var col1, col2;
+ if (category === "modules") {
+ col1 = "Module";
+ } else if (category === "packages") {
+ col1 = "Module";
+ col2 = "Package";
+ } else if (category === "types") {
+ col1 = "Package";
+ col2 = "Class"
+ } else if (category === "members") {
+ col1 = "Class";
+ col2 = "Member";
+ } else if (category === "searchTags") {
+ col1 = "Location";
+ col2 = "Name";
+ }
+ $("").appendTo(table);
+ if (category !== "modules") {
+ $("").appendTo(table);
+ }
+ $.each(items, function(index, item) {
+ var rowColor = index % 2 ? "odd-row-color" : "even-row-color";
+ renderItem(item, table, rowColor);
+ });
+ return table;
+ }
+ function renderItem(item, table, rowColor) {
+ var label = getHighlightedText(item.input, item.boundaries, item.prefix.length, item.input.length);
+ var link = $("
")
+ .attr("href", getURL(item.indexItem, item.category))
+ .attr("tabindex", "0")
+ .addClass("search-result-link")
+ .html(label);
+ var container = getHighlightedText(item.input, item.boundaries, 0, item.prefix.length - 1);
+ if (item.category === "searchTags") {
+ container = item.indexItem.h || "";
+ }
+ if (item.category !== "modules") {
+ $("
").html(container).addClass("col-plain").addClass(rowColor).appendTo(table);
+ }
+ $("
").html(link).addClass("col-last").addClass(rowColor).appendTo(table);
+ }
+ var timeout;
+ function schedulePageSearch() {
+ if (timeout) {
+ clearTimeout(timeout);
+ }
+ timeout = setTimeout(function () {
+ doPageSearch()
+ }, 100);
+ }
+ function doPageSearch() {
+ setSearchUrl();
+ var term = searchTerm = input.val().trim();
+ if (term === "") {
+ notify.html(messages.enterTerm);
+ activeTab = "";
+ fixedTab = false;
+ resultContainer.empty();
+ resultSection.hide();
+ } else {
+ notify.html(messages.searching);
+ doSearch({ term: term, maxResults: 1200 }, renderResults);
+ }
+ }
+ function setSearchUrl() {
+ var query = input.val().trim();
+ var url = document.location.pathname;
+ if (query) {
+ url += "?q=" + encodeURI(query);
+ if (activeTab && fixedTab) {
+ url += "&c=" + activeTab;
+ }
+ }
+ history.replaceState({query: query}, "", url);
+ }
+ input.on("input", function(e) {
+ feelingLucky = false;
+ schedulePageSearch();
+ });
+ $(document).keydown(function(e) {
+ if ((e.ctrlKey || e.metaKey) && (e.key === "ArrowLeft" || e.key === "ArrowRight")) {
+ if (activeTab && visibleTabs.length > 1) {
+ var idx = visibleTabs.indexOf(activeTab);
+ idx += e.key === "ArrowLeft" ? visibleTabs.length - 1 : 1;
+ selectTab(visibleTabs[idx % visibleTabs.length]);
+ return false;
+ }
+ }
+ });
+ reset.click(function() {
+ notify.html(messages.enterTerm);
+ resultSection.hide();
+ activeTab = "";
+ fixedTab = false;
+ resultContainer.empty();
+ input.val('').focus();
+ setSearchUrl();
+ });
+ input.prop("disabled", false);
+ reset.prop("disabled", false);
+
+ var urlParams = new URLSearchParams(window.location.search);
+ if (urlParams.has("q")) {
+ input.val(urlParams.get("q"))
+ }
+ if (urlParams.has("c")) {
+ activeTab = urlParams.get("c");
+ fixedTab = true;
+ }
+ if (urlParams.get("r")) {
+ feelingLucky = true;
+ }
+ if (input.val()) {
+ doPageSearch();
+ } else {
+ notify.html(messages.enterTerm);
+ }
+ input.select().focus();
+});
diff --git a/build/docs/javadoc/search.html b/build/docs/javadoc/search.html
new file mode 100644
index 0000000..953e8e8
--- /dev/null
+++ b/build/docs/javadoc/search.html
@@ -0,0 +1,70 @@
+
+
+
+
+
搜索 (PlayerBlockLife 2.2.0-alpha-1.20.4 API)
+
+
+
+
+
+
+
+
+
+
+
+
+
+您的浏览器已禁用 JavaScript。
+
+
+
+
+
+搜索
+
+
+
+
+其他资源
+
+
+
+
帮助页 介绍了 JavaDoc 搜索的范围和语法。
+
您可以使用 <ctrl> 或 <cmd> 键与左箭头和右箭头键组合在此页面中的结果选项卡之间切换。
+
下面的 URL 模板可用于在支持此功能的浏览器中将此页面配置为搜索引擎。已经对其进行了测试以在 Google Chrome 和 Mozilla Firefox 中使用。请注意,其他浏览器可能不支持此功能或需要不同的 URL 格式。
+
link 复制
+
+
+重定向到第一个结果
+
+正在加载搜索索引...
+
+
+
+
+
+
diff --git a/build/docs/javadoc/search.js b/build/docs/javadoc/search.js
new file mode 100644
index 0000000..d675d24
--- /dev/null
+++ b/build/docs/javadoc/search.js
@@ -0,0 +1,458 @@
+/*
+ * Copyright (c) 2015, 2023, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+"use strict";
+const messages = {
+ enterTerm: "输入搜索词",
+ noResult: "未找到结果",
+ oneResult: "找到一个结果",
+ manyResults: "找到 {0} 个结果",
+ loading: "正在加载搜索索引...",
+ searching: "正在搜索...",
+ redirecting: "正在重定向到第一个结果...",
+ linkIcon: "链接图标",
+ linkToSection: "链接到此节"
+}
+const categories = {
+ modules: "模块",
+ packages: "程序包",
+ types: "类和接口",
+ members: "成员",
+ searchTags: "搜索标记"
+};
+const highlight = "
$& ";
+const NO_MATCH = {};
+const MAX_RESULTS = 300;
+function checkUnnamed(name, separator) {
+ return name === "
" || !name ? "" : name + separator;
+}
+function escapeHtml(str) {
+ return str.replace(//g, ">");
+}
+function getHighlightedText(str, boundaries, from, to) {
+ var start = from;
+ var text = "";
+ for (var i = 0; i < boundaries.length; i += 2) {
+ var b0 = boundaries[i];
+ var b1 = boundaries[i + 1];
+ if (b0 >= to || b1 <= from) {
+ continue;
+ }
+ text += escapeHtml(str.slice(start, Math.max(start, b0)));
+ text += "";
+ text += escapeHtml(str.slice(Math.max(start, b0), Math.min(to, b1)));
+ text += " ";
+ start = Math.min(to, b1);
+ }
+ text += escapeHtml(str.slice(start, to));
+ return text;
+}
+function getURLPrefix(item, category) {
+ var urlPrefix = "";
+ var slash = "/";
+ if (category === "modules") {
+ return item.l + slash;
+ } else if (category === "packages" && item.m) {
+ return item.m + slash;
+ } else if (category === "types" || category === "members") {
+ if (item.m) {
+ urlPrefix = item.m + slash;
+ } else {
+ $.each(packageSearchIndex, function(index, it) {
+ if (it.m && item.p === it.l) {
+ urlPrefix = it.m + slash;
+ }
+ });
+ }
+ }
+ return urlPrefix;
+}
+function getURL(item, category) {
+ if (item.url) {
+ return item.url;
+ }
+ var url = getURLPrefix(item, category);
+ if (category === "modules") {
+ url += "module-summary.html";
+ } else if (category === "packages") {
+ if (item.u) {
+ url = item.u;
+ } else {
+ url += item.l.replace(/\./g, '/') + "/package-summary.html";
+ }
+ } else if (category === "types") {
+ if (item.u) {
+ url = item.u;
+ } else {
+ url += checkUnnamed(item.p, "/").replace(/\./g, '/') + item.l + ".html";
+ }
+ } else if (category === "members") {
+ url += checkUnnamed(item.p, "/").replace(/\./g, '/') + item.c + ".html" + "#";
+ if (item.u) {
+ url += item.u;
+ } else {
+ url += item.l;
+ }
+ } else if (category === "searchTags") {
+ url += item.u;
+ }
+ item.url = url;
+ return url;
+}
+function createMatcher(term, camelCase) {
+ if (camelCase && !isUpperCase(term)) {
+ return null; // no need for camel-case matcher for lower case query
+ }
+ var pattern = "";
+ var upperCase = [];
+ term.trim().split(/\s+/).forEach(function(w, index, array) {
+ var tokens = w.split(/(?=[A-Z,.()<>?[\/])/);
+ for (var i = 0; i < tokens.length; i++) {
+ var s = tokens[i];
+ // ',' and '?' are the only delimiters commonly followed by space in java signatures
+ pattern += "(" + $.ui.autocomplete.escapeRegex(s).replace(/[,?]/g, "$&\\s*?") + ")";
+ upperCase.push(false);
+ var isWordToken = /\w$/.test(s);
+ if (isWordToken) {
+ if (i === tokens.length - 1 && index < array.length - 1) {
+ // space in query string matches all delimiters
+ pattern += "(.*?)";
+ upperCase.push(isUpperCase(s[0]));
+ } else {
+ if (!camelCase && isUpperCase(s) && s.length === 1) {
+ pattern += "()";
+ } else {
+ pattern += "([a-z0-9$<>?[\\]]*?)";
+ }
+ upperCase.push(isUpperCase(s[0]));
+ }
+ } else {
+ pattern += "()";
+ upperCase.push(false);
+ }
+ }
+ });
+ var re = new RegExp(pattern, "gi");
+ re.upperCase = upperCase;
+ return re;
+}
+function findMatch(matcher, input, startOfName, endOfName) {
+ var from = startOfName;
+ matcher.lastIndex = from;
+ var match = matcher.exec(input);
+ // Expand search area until we get a valid result or reach the beginning of the string
+ while (!match || match.index + match[0].length < startOfName || endOfName < match.index) {
+ if (from === 0) {
+ return NO_MATCH;
+ }
+ from = input.lastIndexOf(".", from - 2) + 1;
+ matcher.lastIndex = from;
+ match = matcher.exec(input);
+ }
+ var boundaries = [];
+ var matchEnd = match.index + match[0].length;
+ var score = 5;
+ var start = match.index;
+ var prevEnd = -1;
+ for (var i = 1; i < match.length; i += 2) {
+ var isUpper = isUpperCase(input[start]);
+ var isMatcherUpper = matcher.upperCase[i];
+ // capturing groups come in pairs, match and non-match
+ boundaries.push(start, start + match[i].length);
+ // make sure groups are anchored on a left word boundary
+ var prevChar = input[start - 1] || "";
+ var nextChar = input[start + 1] || "";
+ if (start !== 0 && !/[\W_]/.test(prevChar) && !/[\W_]/.test(input[start])) {
+ if (isUpper && (isLowerCase(prevChar) || isLowerCase(nextChar))) {
+ score -= 0.1;
+ } else if (isMatcherUpper && start === prevEnd) {
+ score -= isUpper ? 0.1 : 1.0;
+ } else {
+ return NO_MATCH;
+ }
+ }
+ prevEnd = start + match[i].length;
+ start += match[i].length + match[i + 1].length;
+
+ // lower score for parts of the name that are missing
+ if (match[i + 1] && prevEnd < endOfName) {
+ score -= rateNoise(match[i + 1]);
+ }
+ }
+ // lower score if a type name contains unmatched camel-case parts
+ if (input[matchEnd - 1] !== "." && endOfName > matchEnd)
+ score -= rateNoise(input.slice(matchEnd, endOfName));
+ score -= rateNoise(input.slice(0, Math.max(startOfName, match.index)));
+
+ if (score <= 0) {
+ return NO_MATCH;
+ }
+ return {
+ input: input,
+ score: score,
+ boundaries: boundaries
+ };
+}
+function isUpperCase(s) {
+ return s !== s.toLowerCase();
+}
+function isLowerCase(s) {
+ return s !== s.toUpperCase();
+}
+function rateNoise(str) {
+ return (str.match(/([.(])/g) || []).length / 5
+ + (str.match(/([A-Z]+)/g) || []).length / 10
+ + str.length / 20;
+}
+function doSearch(request, response) {
+ var term = request.term.trim();
+ var maxResults = request.maxResults || MAX_RESULTS;
+ if (term.length === 0) {
+ return this.close();
+ }
+ var matcher = {
+ plainMatcher: createMatcher(term, false),
+ camelCaseMatcher: createMatcher(term, true)
+ }
+ var indexLoaded = indexFilesLoaded();
+
+ function getPrefix(item, category) {
+ switch (category) {
+ case "packages":
+ return checkUnnamed(item.m, "/");
+ case "types":
+ return checkUnnamed(item.p, ".");
+ case "members":
+ return checkUnnamed(item.p, ".") + item.c + ".";
+ default:
+ return "";
+ }
+ }
+ function useQualifiedName(category) {
+ switch (category) {
+ case "packages":
+ return /[\s/]/.test(term);
+ case "types":
+ case "members":
+ return /[\s.]/.test(term);
+ default:
+ return false;
+ }
+ }
+ function searchIndex(indexArray, category) {
+ var matches = [];
+ if (!indexArray) {
+ if (!indexLoaded) {
+ matches.push({ l: messages.loading, category: category });
+ }
+ return matches;
+ }
+ $.each(indexArray, function (i, item) {
+ var prefix = getPrefix(item, category);
+ var simpleName = item.l;
+ var qualifiedName = prefix + simpleName;
+ var useQualified = useQualifiedName(category);
+ var input = useQualified ? qualifiedName : simpleName;
+ var startOfName = useQualified ? prefix.length : 0;
+ var endOfName = category === "members" && input.indexOf("(", startOfName) > -1
+ ? input.indexOf("(", startOfName) : input.length;
+ var m = findMatch(matcher.plainMatcher, input, startOfName, endOfName);
+ if (m === NO_MATCH && matcher.camelCaseMatcher) {
+ m = findMatch(matcher.camelCaseMatcher, input, startOfName, endOfName);
+ }
+ if (m !== NO_MATCH) {
+ m.indexItem = item;
+ m.prefix = prefix;
+ m.category = category;
+ if (!useQualified) {
+ m.input = qualifiedName;
+ m.boundaries = m.boundaries.map(function(b) {
+ return b + prefix.length;
+ });
+ }
+ matches.push(m);
+ }
+ return true;
+ });
+ return matches.sort(function(e1, e2) {
+ return e2.score - e1.score;
+ }).slice(0, maxResults);
+ }
+
+ var result = searchIndex(moduleSearchIndex, "modules")
+ .concat(searchIndex(packageSearchIndex, "packages"))
+ .concat(searchIndex(typeSearchIndex, "types"))
+ .concat(searchIndex(memberSearchIndex, "members"))
+ .concat(searchIndex(tagSearchIndex, "searchTags"));
+
+ if (!indexLoaded) {
+ updateSearchResults = function() {
+ doSearch(request, response);
+ }
+ } else {
+ updateSearchResults = function() {};
+ }
+ response(result);
+}
+// JQuery search menu implementation
+$.widget("custom.catcomplete", $.ui.autocomplete, {
+ _create: function() {
+ this._super();
+ this.widget().menu("option", "items", "> .result-item");
+ // workaround for search result scrolling
+ this.menu._scrollIntoView = function _scrollIntoView( item ) {
+ var borderTop, paddingTop, offset, scroll, elementHeight, itemHeight;
+ if ( this._hasScroll() ) {
+ borderTop = parseFloat( $.css( this.activeMenu[ 0 ], "borderTopWidth" ) ) || 0;
+ paddingTop = parseFloat( $.css( this.activeMenu[ 0 ], "paddingTop" ) ) || 0;
+ offset = item.offset().top - this.activeMenu.offset().top - borderTop - paddingTop;
+ scroll = this.activeMenu.scrollTop();
+ elementHeight = this.activeMenu.height() - 26;
+ itemHeight = item.outerHeight();
+
+ if ( offset < 0 ) {
+ this.activeMenu.scrollTop( scroll + offset );
+ } else if ( offset + itemHeight > elementHeight ) {
+ this.activeMenu.scrollTop( scroll + offset - elementHeight + itemHeight );
+ }
+ }
+ };
+ },
+ _renderMenu: function(ul, items) {
+ var currentCategory = "";
+ var widget = this;
+ widget.menu.bindings = $();
+ $.each(items, function(index, item) {
+ if (item.category && item.category !== currentCategory) {
+ ul.append("" + categories[item.category] + " ");
+ currentCategory = item.category;
+ }
+ var li = widget._renderItemData(ul, item);
+ if (item.category) {
+ li.attr("aria-label", categories[item.category] + " : " + item.l);
+ } else {
+ li.attr("aria-label", item.l);
+ }
+ li.attr("class", "result-item");
+ });
+ ul.append("Go to search page ");
+ },
+ _renderItem: function(ul, item) {
+ var li = $(" ").appendTo(ul);
+ var div = $("
").appendTo(li);
+ var label = item.l
+ ? item.l
+ : getHighlightedText(item.input, item.boundaries, 0, item.input.length);
+ var idx = item.indexItem;
+ if (item.category === "searchTags" && idx && idx.h) {
+ if (idx.d) {
+ div.html(label + " (" + idx.h + ") "
+ + idx.d + " ");
+ } else {
+ div.html(label + " (" + idx.h + ") ");
+ }
+ } else {
+ div.html(label);
+ }
+ return li;
+ }
+});
+$(function() {
+ var expanded = false;
+ var windowWidth;
+ function collapse() {
+ if (expanded) {
+ $("div#navbar-top").removeAttr("style");
+ $("button#navbar-toggle-button")
+ .removeClass("expanded")
+ .attr("aria-expanded", "false");
+ expanded = false;
+ }
+ }
+ $("button#navbar-toggle-button").click(function (e) {
+ if (expanded) {
+ collapse();
+ } else {
+ var navbar = $("div#navbar-top");
+ navbar.height(navbar.prop("scrollHeight"));
+ $("button#navbar-toggle-button")
+ .addClass("expanded")
+ .attr("aria-expanded", "true");
+ expanded = true;
+ windowWidth = window.innerWidth;
+ }
+ });
+ $("ul.sub-nav-list-small li a").click(collapse);
+ $("input#search-input").focus(collapse);
+ $("main").click(collapse);
+ $("section[id] > :header, :header[id], :header:has(a[id])").each(function(idx, el) {
+ // Create anchor links for headers with an associated id attribute
+ var hdr = $(el);
+ var id = hdr.attr("id") || hdr.parent("section").attr("id") || hdr.children("a").attr("id");
+ if (id) {
+ hdr.append(" ");
+ }
+ });
+ $(window).on("orientationchange", collapse).on("resize", function(e) {
+ if (expanded && windowWidth !== window.innerWidth) collapse();
+ });
+ var search = $("#search-input");
+ var reset = $("#reset-button");
+ search.catcomplete({
+ minLength: 1,
+ delay: 200,
+ source: doSearch,
+ response: function(event, ui) {
+ if (!ui.content.length) {
+ ui.content.push({ l: messages.noResult });
+ } else {
+ $("#search-input").empty();
+ }
+ },
+ autoFocus: true,
+ focus: function(event, ui) {
+ return false;
+ },
+ position: {
+ collision: "flip"
+ },
+ select: function(event, ui) {
+ if (ui.item.indexItem) {
+ var url = getURL(ui.item.indexItem, ui.item.category);
+ window.location.href = pathtoroot + url;
+ $("#search-input").focus();
+ }
+ }
+ });
+ search.val('');
+ search.prop("disabled", false);
+ reset.prop("disabled", false);
+ reset.click(function() {
+ search.val('').focus();
+ });
+ search.focus();
+});
diff --git a/build/docs/javadoc/stylesheet.css b/build/docs/javadoc/stylesheet.css
new file mode 100644
index 0000000..f71489f
--- /dev/null
+++ b/build/docs/javadoc/stylesheet.css
@@ -0,0 +1,1272 @@
+/*
+ * Javadoc style sheet
+ */
+
+@import url('resources/fonts/dejavu.css');
+
+/*
+ * These CSS custom properties (variables) define the core color and font
+ * properties used in this stylesheet.
+ */
+:root {
+ /* body, block and code fonts */
+ --body-font-family: 'DejaVu Sans', Arial, Helvetica, sans-serif;
+ --block-font-family: 'DejaVu Serif', Georgia, "Times New Roman", Times, serif;
+ --code-font-family: 'DejaVu Sans Mono', monospace;
+ /* Base font sizes for body and code elements */
+ --body-font-size: 14px;
+ --code-font-size: 14px;
+ /* Text colors for body and block elements */
+ --body-text-color: #353833;
+ --block-text-color: #474747;
+ /* Background colors for various structural elements */
+ --body-background-color: #ffffff;
+ --section-background-color: #f8f8f8;
+ --detail-background-color: #ffffff;
+ /* Colors for navigation bar and table captions */
+ --navbar-background-color: #4D7A97;
+ --navbar-text-color: #ffffff;
+ /* Background color for subnavigation and various headers */
+ --subnav-background-color: #dee3e9;
+ /* Background and text colors for selected tabs and navigation items */
+ --selected-background-color: #f8981d;
+ --selected-text-color: #253441;
+ --selected-link-color: #1f389c;
+ /* Background colors for generated tables */
+ --even-row-color: #ffffff;
+ --odd-row-color: #eeeeef;
+ /* Text color for page title */
+ --title-color: #2c4557;
+ /* Text colors for links */
+ --link-color: #4A6782;
+ --link-color-active: #bb7a2a;
+ /* Snippet colors */
+ --snippet-background-color: #ebecee;
+ --snippet-text-color: var(--block-text-color);
+ --snippet-highlight-color: #f7c590;
+ /* Border colors for structural elements and user defined tables */
+ --border-color: #ededed;
+ --table-border-color: #000000;
+ /* Search input colors */
+ --search-input-background-color: #ffffff;
+ --search-input-text-color: #000000;
+ --search-input-placeholder-color: #909090;
+ /* Highlight color for active search tag target */
+ --search-tag-highlight-color: #ffff00;
+ /* Adjustments for icon and active background colors of copy-to-clipboard buttons */
+ --copy-icon-brightness: 100%;
+ --copy-button-background-color-active: rgba(168, 168, 176, 0.3);
+ /* Colors for invalid tag notifications */
+ --invalid-tag-background-color: #ffe6e6;
+ --invalid-tag-text-color: #000000;
+}
+/*
+ * Styles for individual HTML elements.
+ *
+ * These are styles that are specific to individual HTML elements. Changing them affects the style of a particular
+ * HTML element throughout the page.
+ */
+body {
+ background-color:var(--body-background-color);
+ color:var(--body-text-color);
+ font-family:var(--body-font-family);
+ font-size:var(--body-font-size);
+ margin:0;
+ padding:0;
+ height:100%;
+ width:100%;
+}
+iframe {
+ margin:0;
+ padding:0;
+ height:100%;
+ width:100%;
+ overflow-y:scroll;
+ border:none;
+}
+a:link, a:visited {
+ text-decoration:none;
+ color:var(--link-color);
+}
+a[href]:hover, a[href]:focus {
+ text-decoration:none;
+ color:var(--link-color-active);
+}
+pre {
+ font-family:var(--code-font-family);
+ font-size:1em;
+}
+h1 {
+ font-size:1.428em;
+}
+h2 {
+ font-size:1.285em;
+}
+h3 {
+ font-size:1.14em;
+}
+h4 {
+ font-size:1.072em;
+}
+h5 {
+ font-size:1.001em;
+}
+h6 {
+ font-size:0.93em;
+}
+/* Disable font boosting for selected elements */
+h1, h2, h3, h4, h5, h6, div.member-signature {
+ max-height: 1000em;
+}
+ul {
+ list-style-type:disc;
+}
+code, tt {
+ font-family:var(--code-font-family);
+}
+:not(h1, h2, h3, h4, h5, h6) > code,
+:not(h1, h2, h3, h4, h5, h6) > tt {
+ font-size:var(--code-font-size);
+ padding-top:4px;
+ margin-top:8px;
+ line-height:1.4em;
+}
+dt code {
+ font-family:var(--code-font-family);
+ font-size:1em;
+ padding-top:4px;
+}
+.summary-table dt code {
+ font-family:var(--code-font-family);
+ font-size:1em;
+ vertical-align:top;
+ padding-top:4px;
+}
+sup {
+ font-size:8px;
+}
+button {
+ font-family: var(--body-font-family);
+ font-size: 1em;
+}
+/*
+ * Styles for HTML generated by javadoc.
+ *
+ * These are style classes that are used by the standard doclet to generate HTML documentation.
+ */
+
+/*
+ * Styles for document title and copyright.
+ */
+.about-language {
+ float:right;
+ padding:0 21px 8px 8px;
+ font-size:0.915em;
+ margin-top:-9px;
+ height:2.9em;
+}
+.legal-copy {
+ margin-left:.5em;
+}
+/*
+ * Styles for navigation bar.
+ */
+@media screen {
+ div.flex-box {
+ position:fixed;
+ display:flex;
+ flex-direction:column;
+ height: 100%;
+ width: 100%;
+ }
+ header.flex-header {
+ flex: 0 0 auto;
+ }
+ div.flex-content {
+ flex: 1 1 auto;
+ overflow-y: auto;
+ }
+}
+.top-nav {
+ background-color:var(--navbar-background-color);
+ color:var(--navbar-text-color);
+ float:left;
+ width:100%;
+ clear:right;
+ min-height:2.8em;
+ padding:10px 0 0 0;
+ overflow:hidden;
+ font-size:0.857em;
+}
+button#navbar-toggle-button {
+ display:none;
+}
+ul.sub-nav-list-small {
+ display: none;
+}
+.sub-nav {
+ background-color:var(--subnav-background-color);
+ float:left;
+ width:100%;
+ overflow:hidden;
+ font-size:0.857em;
+}
+.sub-nav div {
+ clear:left;
+ float:left;
+ padding:6px;
+ text-transform:uppercase;
+}
+.sub-nav .sub-nav-list {
+ padding-top:4px;
+}
+ul.nav-list {
+ display:block;
+ margin:0 25px 0 0;
+ padding:0;
+}
+ul.sub-nav-list {
+ float:left;
+ margin:0 25px 0 0;
+ padding:0;
+}
+ul.nav-list li {
+ list-style:none;
+ float:left;
+ padding: 5px 6px;
+ text-transform:uppercase;
+}
+.sub-nav .nav-list-search {
+ float:right;
+ margin:0;
+ padding:6px;
+ clear:none;
+ text-align:right;
+ position:relative;
+}
+ul.sub-nav-list li {
+ list-style:none;
+ float:left;
+}
+.top-nav a:link, .top-nav a:active, .top-nav a:visited {
+ color:var(--navbar-text-color);
+ text-decoration:none;
+ text-transform:uppercase;
+}
+.top-nav a:hover {
+ color:var(--link-color-active);
+}
+.nav-bar-cell1-rev {
+ background-color:var(--selected-background-color);
+ color:var(--selected-text-color);
+ margin: auto 5px;
+}
+.skip-nav {
+ position:absolute;
+ top:auto;
+ left:-9999px;
+ overflow:hidden;
+}
+/*
+ * Hide navigation links and search box in print layout
+ */
+@media print {
+ ul.nav-list, div.sub-nav {
+ display:none;
+ }
+}
+/*
+ * Styles for page header.
+ */
+.title {
+ color:var(--title-color);
+ margin:10px 0;
+}
+.sub-title {
+ margin:5px 0 0 0;
+}
+ul.contents-list {
+ margin: 0 0 15px 0;
+ padding: 0;
+ list-style: none;
+}
+ul.contents-list li {
+ font-size:0.93em;
+}
+/*
+ * Styles for headings.
+ */
+body.class-declaration-page .summary h2,
+body.class-declaration-page .details h2,
+body.class-use-page h2,
+body.module-declaration-page .block-list h2 {
+ font-style: italic;
+ padding:0;
+ margin:15px 0;
+}
+body.class-declaration-page .summary h3,
+body.class-declaration-page .details h3,
+body.class-declaration-page .summary .inherited-list h2 {
+ background-color:var(--subnav-background-color);
+ border:1px solid var(--border-color);
+ margin:0 0 6px -8px;
+ padding:7px 5px;
+}
+/*
+ * Styles for page layout containers.
+ */
+main {
+ clear:both;
+ padding:10px 20px;
+ position:relative;
+}
+dl.notes > dt {
+ font-family: var(--body-font-family);
+ font-size:0.856em;
+ font-weight:bold;
+ margin:10px 0 0 0;
+ color:var(--body-text-color);
+}
+dl.notes > dd {
+ margin:5px 10px 10px 0;
+ font-size:1em;
+ font-family:var(--block-font-family)
+}
+dl.name-value > dt {
+ margin-left:1px;
+ font-size:1.1em;
+ display:inline;
+ font-weight:bold;
+}
+dl.name-value > dd {
+ margin:0 0 0 1px;
+ font-size:1.1em;
+ display:inline;
+}
+/*
+ * Styles for lists.
+ */
+li.circle {
+ list-style:circle;
+}
+ul.horizontal li {
+ display:inline;
+ font-size:0.9em;
+}
+div.inheritance {
+ margin:0;
+ padding:0;
+}
+div.inheritance div.inheritance {
+ margin-left:2em;
+}
+ul.block-list,
+ul.details-list,
+ul.member-list,
+ul.summary-list {
+ margin:10px 0 10px 0;
+ padding:0;
+}
+ul.block-list > li,
+ul.details-list > li,
+ul.member-list > li,
+ul.summary-list > li {
+ list-style:none;
+ margin-bottom:15px;
+ line-height:1.4;
+}
+ul.ref-list {
+ padding:0;
+ margin:0;
+}
+ul.ref-list > li {
+ list-style:none;
+}
+.summary-table dl, .summary-table dl dt, .summary-table dl dd {
+ margin-top:0;
+ margin-bottom:1px;
+}
+ul.tag-list, ul.tag-list-long {
+ padding-left: 0;
+ list-style: none;
+}
+ul.tag-list li {
+ display: inline;
+}
+ul.tag-list li:not(:last-child):after,
+ul.tag-list-long li:not(:last-child):after
+{
+ content: ", ";
+ white-space: pre-wrap;
+}
+ul.preview-feature-list {
+ list-style: none;
+ margin:0;
+ padding:0.1em;
+ line-height: 1.6em;
+}
+/*
+ * Styles for tables.
+ */
+.summary-table, .details-table {
+ width:100%;
+ border-spacing:0;
+ border:1px solid var(--border-color);
+ border-top:0;
+ padding:0;
+}
+.caption {
+ position:relative;
+ text-align:left;
+ background-repeat:no-repeat;
+ color:var(--selected-text-color);
+ clear:none;
+ overflow:hidden;
+ padding: 10px 0 0 1px;
+ margin:0;
+}
+.caption a:link, .caption a:visited {
+ color:var(--selected-link-color);
+}
+.caption a:hover,
+.caption a:active {
+ color:var(--navbar-text-color);
+}
+.caption span {
+ font-weight:bold;
+ white-space:nowrap;
+ padding:5px 12px 7px 12px;
+ display:inline-block;
+ float:left;
+ background-color:var(--selected-background-color);
+ border: none;
+ height:16px;
+}
+div.table-tabs {
+ padding:10px 0 0 1px;
+ margin:10px 0 0 0;
+}
+div.table-tabs > button {
+ border: none;
+ cursor: pointer;
+ padding: 5px 12px 7px 12px;
+ font-weight: bold;
+ margin-right: 8px;
+}
+div.table-tabs > .active-table-tab {
+ background: var(--selected-background-color);
+ color: var(--selected-text-color);
+}
+div.table-tabs > button.table-tab {
+ background: var(--navbar-background-color);
+ color: var(--navbar-text-color);
+}
+.two-column-search-results {
+ display: grid;
+ grid-template-columns: minmax(400px, max-content) minmax(400px, auto);
+}
+div.checkboxes {
+ line-height: 2em;
+}
+div.checkboxes > span {
+ margin-left: 10px;
+}
+div.checkboxes > label {
+ margin-left: 8px;
+ white-space: nowrap;
+}
+div.checkboxes > label > input {
+ margin: 0 2px;
+}
+.two-column-summary {
+ display: grid;
+ grid-template-columns: minmax(25%, max-content) minmax(25%, auto);
+}
+.three-column-summary {
+ display: grid;
+ grid-template-columns: minmax(15%, max-content) minmax(20%, max-content) minmax(20%, auto);
+}
+.three-column-release-summary {
+ display: grid;
+ grid-template-columns: minmax(40%, max-content) minmax(10%, max-content) minmax(40%, auto);
+}
+.four-column-summary {
+ display: grid;
+ grid-template-columns: minmax(10%, max-content) minmax(15%, max-content) minmax(15%, max-content) minmax(15%, auto);
+}
+@media screen and (max-width: 1000px) {
+ .four-column-summary {
+ display: grid;
+ grid-template-columns: minmax(15%, max-content) minmax(15%, auto);
+ }
+}
+@media screen and (max-width: 800px) {
+ .two-column-search-results {
+ display: grid;
+ grid-template-columns: minmax(40%, max-content) minmax(40%, auto);
+ }
+ .three-column-summary {
+ display: grid;
+ grid-template-columns: minmax(10%, max-content) minmax(25%, auto);
+ }
+ .three-column-release-summary {
+ display: grid;
+ grid-template-columns: minmax(70%, max-content) minmax(30%, max-content)
+ }
+ .three-column-summary .col-last,
+ .three-column-release-summary .col-last{
+ grid-column-end: span 2;
+ }
+}
+@media screen and (max-width: 600px) {
+ .two-column-summary {
+ display: grid;
+ grid-template-columns: 1fr;
+ }
+}
+.summary-table > div, .details-table > div {
+ text-align:left;
+ padding: 8px 3px 3px 7px;
+ overflow-x: auto;
+ scrollbar-width: thin;
+}
+.col-first, .col-second, .col-last, .col-constructor-name, .col-summary-item-name {
+ vertical-align:top;
+ padding-right:0;
+ padding-top:8px;
+ padding-bottom:3px;
+}
+.table-header {
+ background:var(--subnav-background-color);
+ font-weight: bold;
+}
+/* Sortable table columns */
+.table-header[onclick] {
+ cursor: pointer;
+}
+.table-header[onclick]::after {
+ content:"";
+ display:inline-block;
+ background-image:url('data:image/svg+xml; utf8, \
+ \
+ ');
+ background-size:100% 100%;
+ width:9px;
+ height:14px;
+ margin-left:4px;
+ margin-bottom:-3px;
+}
+.table-header[onclick].sort-asc::after {
+ background-image:url('data:image/svg+xml; utf8, \
+ \
+ \
+ ');
+
+}
+.table-header[onclick].sort-desc::after {
+ background-image:url('data:image/svg+xml; utf8, \
+ \
+ \
+ ');
+}
+.col-first, .col-first {
+ font-size:0.93em;
+}
+.col-second, .col-second, .col-last, .col-constructor-name, .col-summary-item-name, .col-last {
+ font-size:0.93em;
+}
+.col-first, .col-second, .col-constructor-name {
+ vertical-align:top;
+ overflow: auto;
+}
+.col-last {
+ white-space:normal;
+}
+.col-first a:link, .col-first a:visited,
+.col-second a:link, .col-second a:visited,
+.col-first a:link, .col-first a:visited,
+.col-second a:link, .col-second a:visited,
+.col-constructor-name a:link, .col-constructor-name a:visited,
+.col-summary-item-name a:link, .col-summary-item-name a:visited {
+ font-weight:bold;
+}
+.even-row-color, .even-row-color .table-header {
+ background-color:var(--even-row-color);
+}
+.odd-row-color, .odd-row-color .table-header {
+ background-color:var(--odd-row-color);
+}
+/*
+ * Styles for contents.
+ */
+div.block {
+ font-size:var(--body-font-size);
+ font-family:var(--block-font-family);
+}
+.col-last div {
+ padding-top:0;
+}
+.col-last a {
+ padding-bottom:3px;
+}
+.module-signature,
+.package-signature,
+.type-signature,
+.member-signature {
+ font-family:var(--code-font-family);
+ font-size:1em;
+ margin:14px 0;
+ white-space: pre-wrap;
+}
+.module-signature,
+.package-signature,
+.type-signature {
+ margin-top: 0;
+}
+.member-signature .type-parameters-long,
+.member-signature .parameters,
+.member-signature .exceptions {
+ display: inline-block;
+ vertical-align: top;
+ white-space: pre;
+}
+.member-signature .type-parameters {
+ white-space: normal;
+}
+/*
+ * Styles for formatting effect.
+ */
+.source-line-no {
+ /* Color of line numbers in source pages can be set via custom property below */
+ color:var(--source-linenumber-color, green);
+ padding:0 30px 0 0;
+}
+.block {
+ display:block;
+ margin:0 10px 5px 0;
+ color:var(--block-text-color);
+}
+.deprecated-label, .description-from-type-label, .implementation-label, .member-name-link,
+.module-label-in-package, .module-label-in-type, .package-label-in-type,
+.package-hierarchy-label, .type-name-label, .type-name-link, .search-tag-link, .preview-label {
+ font-weight:bold;
+}
+.deprecation-comment, .help-footnote, .preview-comment {
+ font-style:italic;
+}
+.deprecation-block {
+ font-size:1em;
+ font-family:var(--block-font-family);
+ border-style:solid;
+ border-width:thin;
+ border-radius:10px;
+ padding:10px;
+ margin-bottom:10px;
+ margin-right:10px;
+ display:inline-block;
+}
+.preview-block {
+ font-size:1em;
+ font-family:var(--block-font-family);
+ border-style:solid;
+ border-width:thin;
+ border-radius:10px;
+ padding:10px;
+ margin-bottom:10px;
+ margin-right:10px;
+ display:inline-block;
+}
+div.block div.deprecation-comment {
+ font-style:normal;
+}
+details.invalid-tag, span.invalid-tag {
+ font-size:1em;
+ font-family:var(--block-font-family);
+ color: var(--invalid-tag-text-color);
+ background: var(--invalid-tag-background-color);
+ border: thin solid var(--table-border-color);
+ border-radius:2px;
+ padding: 2px 4px;
+ display:inline-block;
+}
+details summary {
+ cursor: pointer;
+}
+/*
+ * Styles specific to HTML5 elements.
+ */
+main, nav, header, footer, section {
+ display:block;
+}
+/*
+ * Styles for javadoc search.
+ */
+.ui-state-active {
+ /* Overrides the color of selection used in jQuery UI */
+ background: var(--selected-background-color);
+ border: 1px solid var(--selected-background-color);
+ color: var(--selected-text-color);
+}
+.ui-autocomplete-category {
+ font-weight:bold;
+ font-size:15px;
+ padding:7px 0 7px 3px;
+ background-color:var(--navbar-background-color);
+ color:var(--navbar-text-color);
+}
+.ui-autocomplete {
+ max-height:85%;
+ max-width:65%;
+ overflow-y:auto;
+ overflow-x:auto;
+ scrollbar-width: thin;
+ white-space:nowrap;
+ box-shadow: 0 3px 6px rgba(0,0,0,0.16), 0 3px 6px rgba(0,0,0,0.23);
+}
+ul.ui-autocomplete {
+ position:fixed;
+ z-index:1;
+ background-color: var(--body-background-color);
+}
+ul.ui-autocomplete li {
+ float:left;
+ clear:both;
+ min-width:100%;
+}
+ul.ui-autocomplete li.ui-static-link {
+ position:sticky;
+ bottom:0;
+ left:0;
+ background: var(--subnav-background-color);
+ padding: 5px 0;
+ font-family: var(--body-font-family);
+ font-size: 0.93em;
+ font-weight: bolder;
+ z-index: 2;
+}
+li.ui-static-link a, li.ui-static-link a:visited {
+ text-decoration:none;
+ color:var(--link-color);
+ float:right;
+ margin-right:20px;
+}
+.ui-autocomplete .result-item {
+ font-size: inherit;
+}
+.ui-autocomplete .result-highlight {
+ font-weight:bold;
+}
+#search-input, #page-search-input {
+ background-image:url('resources/glass.png');
+ background-size:13px;
+ background-repeat:no-repeat;
+ background-position:2px 3px;
+ background-color: var(--search-input-background-color);
+ color: var(--search-input-text-color);
+ border-color: var(--border-color);
+ padding-left:20px;
+ width: 250px;
+ margin: 0;
+}
+#search-input {
+ margin-left: 4px;
+}
+#reset-button {
+ background-color: transparent;
+ background-image:url('resources/x.png');
+ background-repeat:no-repeat;
+ background-size:contain;
+ border:0;
+ border-radius:0;
+ width:12px;
+ height:12px;
+ position:absolute;
+ right:12px;
+ top:10px;
+ font-size:0;
+}
+::placeholder {
+ color:var(--search-input-placeholder-color);
+ opacity: 1;
+}
+.search-tag-desc-result {
+ font-style:italic;
+ font-size:11px;
+}
+.search-tag-holder-result {
+ font-style:italic;
+ font-size:12px;
+}
+.search-tag-result:target {
+ background-color:var(--search-tag-highlight-color);
+}
+details.page-search-details {
+ display: inline-block;
+}
+div#result-container {
+ font-size: 1em;
+}
+div#result-container a.search-result-link {
+ padding: 0;
+ margin: 4px 0;
+ width: 100%;
+}
+#result-container .result-highlight {
+ font-weight:bolder;
+}
+.page-search-info {
+ background-color: var(--subnav-background-color);
+ border-radius: 3px;
+ border: 0 solid var(--border-color);
+ padding: 0 8px;
+ overflow: hidden;
+ height: 0;
+ transition: all 0.2s ease;
+}
+div.table-tabs > button.table-tab {
+ background: var(--navbar-background-color);
+ color: var(--navbar-text-color);
+}
+.page-search-header {
+ padding: 5px 12px 7px 12px;
+ font-weight: bold;
+ margin-right: 3px;
+ background-color:var(--navbar-background-color);
+ color:var(--navbar-text-color);
+ display: inline-block;
+}
+button.page-search-header {
+ border: none;
+ cursor: pointer;
+}
+span#page-search-link {
+ text-decoration: underline;
+}
+.module-graph span, .sealed-graph span {
+ display:none;
+ position:absolute;
+}
+.module-graph:hover span, .sealed-graph:hover span {
+ display:block;
+ margin: -100px 0 0 100px;
+ z-index: 1;
+}
+.inherited-list {
+ margin: 10px 0 10px 0;
+}
+section.class-description {
+ line-height: 1.4;
+}
+.summary section[class$="-summary"], .details section[class$="-details"],
+.class-uses .detail, .serialized-class-details {
+ padding: 0 20px 5px 10px;
+ border: 1px solid var(--border-color);
+ background-color: var(--section-background-color);
+}
+.inherited-list, section[class$="-details"] .detail {
+ padding:0 0 5px 8px;
+ background-color:var(--detail-background-color);
+ border:none;
+}
+.vertical-separator {
+ padding: 0 5px;
+}
+ul.help-section-list {
+ margin: 0;
+}
+ul.help-subtoc > li {
+ display: inline-block;
+ padding-right: 5px;
+ font-size: smaller;
+}
+ul.help-subtoc > li::before {
+ content: "\2022" ;
+ padding-right:2px;
+}
+.help-note {
+ font-style: italic;
+}
+/*
+ * Indicator icon for external links.
+ */
+main a[href*="://"]::after {
+ content:"";
+ display:inline-block;
+ background-image:url('data:image/svg+xml; utf8, \
+ \
+ \
+ ');
+ background-size:100% 100%;
+ width:7px;
+ height:7px;
+ margin-left:2px;
+ margin-bottom:4px;
+}
+main a[href*="://"]:hover::after,
+main a[href*="://"]:focus::after {
+ background-image:url('data:image/svg+xml; utf8, \
+ \
+ \
+ ');
+}
+/*
+ * Styles for header/section anchor links
+ */
+a.anchor-link {
+ opacity: 0;
+ transition: opacity 0.1s;
+}
+:hover > a.anchor-link {
+ opacity: 80%;
+}
+a.anchor-link:hover,
+a.anchor-link:focus-visible,
+a.anchor-link.visible {
+ opacity: 100%;
+}
+a.anchor-link > img {
+ width: 0.9em;
+ height: 0.9em;
+}
+/*
+ * Styles for copy-to-clipboard buttons
+ */
+button.copy {
+ opacity: 70%;
+ border: none;
+ border-radius: 3px;
+ position: relative;
+ background:none;
+ transition: opacity 0.3s;
+ cursor: pointer;
+}
+:hover > button.copy {
+ opacity: 80%;
+}
+button.copy:hover,
+button.copy:active,
+button.copy:focus-visible,
+button.copy.visible {
+ opacity: 100%;
+}
+button.copy img {
+ position: relative;
+ background: none;
+ filter: brightness(var(--copy-icon-brightness));
+}
+button.copy:active {
+ background-color: var(--copy-button-background-color-active);
+}
+button.copy span {
+ color: var(--body-text-color);
+ position: relative;
+ top: -0.1em;
+ transition: all 0.1s;
+ font-size: 0.76rem;
+ line-height: 1.2em;
+ opacity: 0;
+}
+button.copy:hover span,
+button.copy:focus-visible span,
+button.copy.visible span {
+ opacity: 100%;
+}
+/* search page copy button */
+button#page-search-copy {
+ margin-left: 0.4em;
+ padding:0.3em;
+ top:0.13em;
+}
+button#page-search-copy img {
+ width: 1.2em;
+ height: 1.2em;
+ padding: 0.01em 0;
+ top: 0.15em;
+}
+button#page-search-copy span {
+ color: var(--body-text-color);
+ line-height: 1.2em;
+ padding: 0.2em;
+ top: -0.18em;
+}
+div.page-search-info:hover button#page-search-copy span {
+ opacity: 100%;
+}
+/* snippet copy button */
+button.snippet-copy {
+ position: absolute;
+ top: 6px;
+ right: 6px;
+ height: 1.7em;
+ padding: 2px;
+}
+button.snippet-copy img {
+ width: 18px;
+ height: 18px;
+ padding: 0.05em 0;
+}
+button.snippet-copy span {
+ line-height: 1.2em;
+ padding: 0.2em;
+ position: relative;
+ top: -0.5em;
+}
+div.snippet-container:hover button.snippet-copy span {
+ opacity: 100%;
+}
+/*
+ * Styles for user-provided tables.
+ *
+ * borderless:
+ * No borders, vertical margins, styled caption.
+ * This style is provided for use with existing doc comments.
+ * In general, borderless tables should not be used for layout purposes.
+ *
+ * plain:
+ * Plain borders around table and cells, vertical margins, styled caption.
+ * Best for small tables or for complex tables for tables with cells that span
+ * rows and columns, when the "striped" style does not work well.
+ *
+ * striped:
+ * Borders around the table and vertical borders between cells, striped rows,
+ * vertical margins, styled caption.
+ * Best for tables that have a header row, and a body containing a series of simple rows.
+ */
+
+table.borderless,
+table.plain,
+table.striped {
+ margin-top: 10px;
+ margin-bottom: 10px;
+}
+table.borderless > caption,
+table.plain > caption,
+table.striped > caption {
+ font-weight: bold;
+ font-size: smaller;
+}
+table.borderless th, table.borderless td,
+table.plain th, table.plain td,
+table.striped th, table.striped td {
+ padding: 2px 5px;
+}
+table.borderless,
+table.borderless > thead > tr > th, table.borderless > tbody > tr > th, table.borderless > tr > th,
+table.borderless > thead > tr > td, table.borderless > tbody > tr > td, table.borderless > tr > td {
+ border: none;
+}
+table.borderless > thead > tr, table.borderless > tbody > tr, table.borderless > tr {
+ background-color: transparent;
+}
+table.plain {
+ border-collapse: collapse;
+ border: 1px solid var(--table-border-color);
+}
+table.plain > thead > tr, table.plain > tbody tr, table.plain > tr {
+ background-color: transparent;
+}
+table.plain > thead > tr > th, table.plain > tbody > tr > th, table.plain > tr > th,
+table.plain > thead > tr > td, table.plain > tbody > tr > td, table.plain > tr > td {
+ border: 1px solid var(--table-border-color);
+}
+table.striped {
+ border-collapse: collapse;
+ border: 1px solid var(--table-border-color);
+}
+table.striped > thead {
+ background-color: var(--subnav-background-color);
+}
+table.striped > thead > tr > th, table.striped > thead > tr > td {
+ border: 1px solid var(--table-border-color);
+}
+table.striped > tbody > tr:nth-child(even) {
+ background-color: var(--odd-row-color)
+}
+table.striped > tbody > tr:nth-child(odd) {
+ background-color: var(--even-row-color)
+}
+table.striped > tbody > tr > th, table.striped > tbody > tr > td {
+ border-left: 1px solid var(--table-border-color);
+ border-right: 1px solid var(--table-border-color);
+}
+table.striped > tbody > tr > th {
+ font-weight: normal;
+}
+/**
+ * Tweak style for small screens.
+ */
+@media screen and (max-width: 920px) {
+ header.flex-header {
+ max-height: 100vh;
+ overflow-y: auto;
+ }
+ div#navbar-top {
+ height: 2.8em;
+ transition: height 0.35s ease;
+ }
+ ul.nav-list {
+ display: block;
+ width: 40%;
+ float:left;
+ clear: left;
+ margin: 10px 0 0 0;
+ padding: 0;
+ }
+ ul.nav-list li {
+ float: none;
+ padding: 6px;
+ margin-left: 10px;
+ margin-top: 2px;
+ }
+ ul.sub-nav-list-small {
+ display:block;
+ height: 100%;
+ width: 50%;
+ float: right;
+ clear: right;
+ background-color: var(--subnav-background-color);
+ color: var(--body-text-color);
+ margin: 6px 0 0 0;
+ padding: 0;
+ }
+ ul.sub-nav-list-small ul {
+ padding-left: 20px;
+ }
+ ul.sub-nav-list-small a:link, ul.sub-nav-list-small a:visited {
+ color:var(--link-color);
+ }
+ ul.sub-nav-list-small a:hover {
+ color:var(--link-color-active);
+ }
+ ul.sub-nav-list-small li {
+ list-style:none;
+ float:none;
+ padding: 6px;
+ margin-top: 1px;
+ text-transform:uppercase;
+ }
+ ul.sub-nav-list-small > li {
+ margin-left: 10px;
+ }
+ ul.sub-nav-list-small li p {
+ margin: 5px 0;
+ }
+ div#navbar-sub-list {
+ display: none;
+ }
+ .top-nav a:link, .top-nav a:active, .top-nav a:visited {
+ display: block;
+ }
+ button#navbar-toggle-button {
+ width: 3.4em;
+ height: 2.8em;
+ background-color: transparent;
+ display: block;
+ float: left;
+ border: 0;
+ margin: 0 10px;
+ cursor: pointer;
+ font-size: 10px;
+ }
+ button#navbar-toggle-button .nav-bar-toggle-icon {
+ display: block;
+ width: 24px;
+ height: 3px;
+ margin: 1px 0 4px 0;
+ border-radius: 2px;
+ transition: all 0.1s;
+ background-color: var(--navbar-text-color);
+ }
+ button#navbar-toggle-button.expanded span.nav-bar-toggle-icon:nth-child(1) {
+ transform: rotate(45deg);
+ transform-origin: 10% 10%;
+ width: 26px;
+ }
+ button#navbar-toggle-button.expanded span.nav-bar-toggle-icon:nth-child(2) {
+ opacity: 0;
+ }
+ button#navbar-toggle-button.expanded span.nav-bar-toggle-icon:nth-child(3) {
+ transform: rotate(-45deg);
+ transform-origin: 10% 90%;
+ width: 26px;
+ }
+}
+@media screen and (max-width: 800px) {
+ .about-language {
+ padding-right: 16px;
+ }
+ ul.nav-list li {
+ margin-left: 5px;
+ }
+ ul.sub-nav-list-small > li {
+ margin-left: 5px;
+ }
+ main {
+ padding: 10px;
+ }
+ .summary section[class$="-summary"], .details section[class$="-details"],
+ .class-uses .detail, .serialized-class-details {
+ padding: 0 8px 5px 8px;
+ }
+ body {
+ -webkit-text-size-adjust: none;
+ }
+}
+@media screen and (max-width: 400px) {
+ .about-language {
+ font-size: 10px;
+ padding-right: 12px;
+ }
+}
+@media screen and (max-width: 400px) {
+ .nav-list-search {
+ width: 94%;
+ }
+ #search-input, #page-search-input {
+ width: 70%;
+ }
+}
+@media screen and (max-width: 320px) {
+ .nav-list-search > label {
+ display: none;
+ }
+ .nav-list-search {
+ width: 90%;
+ }
+ #search-input, #page-search-input {
+ width: 80%;
+ }
+}
+
+pre.snippet {
+ background-color: var(--snippet-background-color);
+ color: var(--snippet-text-color);
+ padding: 10px;
+ margin: 12px 0;
+ overflow: auto;
+ white-space: pre;
+}
+div.snippet-container {
+ position: relative;
+}
+@media screen and (max-width: 800px) {
+ pre.snippet {
+ padding-top: 26px;
+ }
+ button.snippet-copy {
+ top: 4px;
+ right: 4px;
+ }
+}
+pre.snippet .italic {
+ font-style: italic;
+}
+pre.snippet .bold {
+ font-weight: bold;
+}
+pre.snippet .highlighted {
+ background-color: var(--snippet-highlight-color);
+ border-radius: 10%;
+}
diff --git a/build/docs/javadoc/tag-search-index.js b/build/docs/javadoc/tag-search-index.js
new file mode 100644
index 0000000..0367dae
--- /dev/null
+++ b/build/docs/javadoc/tag-search-index.js
@@ -0,0 +1 @@
+tagSearchIndex = [];updateSearchResults();
\ No newline at end of file
diff --git a/build/docs/javadoc/type-search-index.js b/build/docs/javadoc/type-search-index.js
new file mode 100644
index 0000000..b8001f6
--- /dev/null
+++ b/build/docs/javadoc/type-search-index.js
@@ -0,0 +1 @@
+typeSearchIndex = [{"p":"com.playerblocklife","l":"AdminCommands"},{"p":"com.playerblocklife","l":"BlockBreakListener"},{"p":"com.playerblocklife","l":"CheckLifeBlocksCommand"},{"p":"com.playerblocklife","l":"ConfigManager"},{"p":"com.playerblocklife","l":"GameStateManager.GameState"},{"p":"com.playerblocklife","l":"GameStateManager"},{"p":"com.playerblocklife","l":"LifeSystem"},{"p":"com.playerblocklife","l":"MessageManager"},{"p":"com.playerblocklife","l":"PBLCommands"},{"p":"com.playerblocklife","l":"PlayerBlockLife"},{"p":"com.playerblocklife","l":"PlayerBlockManager"},{"p":"com.playerblocklife","l":"PlayerJoinListener"},{"p":"com.playerblocklife","l":"PlayerQuitListener"},{"p":"com.playerblocklife","l":"SetLifeBlocksCommand"},{"p":"com.playerblocklife","l":"SkinManager"},{"l":"所有类和接口","u":"allclasses-index.html"}];updateSearchResults();
\ No newline at end of file
diff --git a/build/libs/PlayerBlockLife-1.0.0-1.20.4-2026_2_13.jar b/build/libs/PlayerBlockLife-1.0.0-1.20.4-2026_2_13.jar
new file mode 100644
index 0000000..e53856a
Binary files /dev/null and b/build/libs/PlayerBlockLife-1.0.0-1.20.4-2026_2_13.jar differ
diff --git a/build/libs/PlayerBlockLife-1.0.1-1.20.4.jar b/build/libs/PlayerBlockLife-1.0.1-1.20.4.jar
new file mode 100644
index 0000000..0866728
Binary files /dev/null and b/build/libs/PlayerBlockLife-1.0.1-1.20.4.jar differ
diff --git a/build/libs/PlayerBlockLife-2.0.0-1.20.4.jar b/build/libs/PlayerBlockLife-2.0.0-1.20.4.jar
new file mode 100644
index 0000000..8e9bc48
Binary files /dev/null and b/build/libs/PlayerBlockLife-2.0.0-1.20.4.jar differ
diff --git a/build/libs/PlayerBlockLife-2.0.1-1.20.4.jar b/build/libs/PlayerBlockLife-2.0.1-1.20.4.jar
new file mode 100644
index 0000000..25ccfc8
Binary files /dev/null and b/build/libs/PlayerBlockLife-2.0.1-1.20.4.jar differ
diff --git a/build/libs/PlayerBlockLife-2.1.0-1.20.4.jar b/build/libs/PlayerBlockLife-2.1.0-1.20.4.jar
new file mode 100644
index 0000000..e5f91b6
Binary files /dev/null and b/build/libs/PlayerBlockLife-2.1.0-1.20.4.jar differ
diff --git a/build/libs/PlayerBlockLife-2.2.0-1.20.4.jar b/build/libs/PlayerBlockLife-2.2.0-1.20.4.jar
new file mode 100644
index 0000000..64bae72
Binary files /dev/null and b/build/libs/PlayerBlockLife-2.2.0-1.20.4.jar differ
diff --git a/build/libs/PlayerBlockLife-2.2.0-alpha-1.20.4.jar b/build/libs/PlayerBlockLife-2.2.0-alpha-1.20.4.jar
new file mode 100644
index 0000000..8654885
Binary files /dev/null and b/build/libs/PlayerBlockLife-2.2.0-alpha-1.20.4.jar differ
diff --git a/build/libs/PlayerBlockLife-2.2.0-experimental-1.20.4.jar b/build/libs/PlayerBlockLife-2.2.0-experimental-1.20.4.jar
new file mode 100644
index 0000000..07abf25
Binary files /dev/null and b/build/libs/PlayerBlockLife-2.2.0-experimental-1.20.4.jar differ
diff --git a/build/resources/main/config.yml b/build/resources/main/config.yml
new file mode 100644
index 0000000..2d6a867
--- /dev/null
+++ b/build/resources/main/config.yml
@@ -0,0 +1,97 @@
+# PlayerBlockLife 配置文件
+config-version: 1
+
+# 游戏规则
+game:
+ # 方块被挖光时是否死亡
+ die_when_blocks_gone: true
+ # 死亡后是否变成观察者
+ become_spectator: true
+ # 是否启用生命值系统
+ health_system: true
+
+ # 广播设置
+ broadcast:
+ # 方块被破坏时是否广播
+ on_block_break: true
+ # 广播范围(格数)
+ range: 30
+ # 玩家死亡时是否全服广播
+ on_player_death: true
+
+ # 挖掘奖励
+ break_rewards:
+ # 是否给予经验
+ give_exp: true
+ # 经验数量
+ exp_amount: 5
+
+# 数据存储
+storage:
+ # 存储类型 (yaml, json, sqlite)
+ type: yaml
+
+ # 自动保存
+ auto_save:
+ # 是否启用自动保存
+ enabled: true
+ # 保存间隔(秒)
+ interval: 300
+
+# 保护设置
+protection:
+ # 是否保护生命方块不被爆炸破坏
+ protect_from_explosions: true
+ # 是否保护生命方块不被火焰烧毁
+ protect_from_fire: true
+ # 是否保护生命方块不被活塞推动
+ protect_from_pistons: true
+
+# 命令启用配置
+commands:
+ # setlifeblocks 命令
+ setlifeblocks:
+ enabled: true
+ # 是否允许玩家自己使用
+ allow_self_use: true
+ # 是否允许管理员为其他玩家设置
+ allow_admin_use: true
+
+ # checklifeblocks 命令
+ checklifeblocks:
+ enabled: true
+ # 是否允许玩家自己查看
+ allow_self_use: true
+ # 是否允许管理员查看其他玩家
+ allow_admin_use: true
+
+ # pblreload 命令
+ pblreload:
+ enabled: true
+ # 仅限管理员使用
+ admin_only: true
+
+ # pbldelete 命令
+ pbldelete:
+ enabled: true
+ # 仅限管理员使用
+ admin_only: true
+
+ # pblrevive 命令
+ pblrevive:
+ enabled: true
+ # 仅限管理员使用
+ admin_only: true
+
+ # pblstats 命令
+ pblstats:
+ enabled: true
+ # 仅限管理员使用
+ admin_only: true
+
+# 消息配置(现在使用独立的 messages.yml 文件)
+messages:
+ # 是否启用独立的消息文件
+ use_external_file: true
+ # 外部消息文件名称
+ external_file: "messages.yml"
\ No newline at end of file
diff --git a/build/resources/main/messages.yml b/build/resources/main/messages.yml
new file mode 100644
index 0000000..ae468ca
--- /dev/null
+++ b/build/resources/main/messages.yml
@@ -0,0 +1,108 @@
+# PlayerBlockLife 消息配置文件
+# 所有插件输出消息都可以在这里自定义
+
+# 控制台消息
+console:
+ plugin_enabled: "&a[PlayerBlockLife] 插件已启用!版本: {version}"
+ plugin_disabled: "&c[PlayerBlockLife] 插件已禁用!"
+ config_reloaded: "&a[PlayerBlockLife] 配置已重载!"
+ player_joined: "&a[PlayerBlockLife] 玩家 {player} 已加入,正在生成生命方块..."
+ blocks_generated: "&a[PlayerBlockLife] 已为玩家 {player} 生成 {amount} 个生命方块"
+ player_eliminated: "&c[PlayerBlockLife] 玩家 {player} 的生命方块已被挖光,已被淘汰!"
+ error_generating_blocks: "&c[PlayerBlockLife] 为玩家 {player} 生成生命方块时出错: {error}"
+
+# 游戏内消息
+game:
+ # 方块相关
+ block:
+ destroyed:
+ owner: "&c⚠ 警告!你的生命方块被 {breaker} 破坏了!剩余: {remaining}/{total}"
+ breaker: "&a你破坏了 {owner} 的生命方块!"
+ all_destroyed: "&c☠ 你的所有生命方块已被破坏!你已被淘汰!"
+ placed: "&a已为你生成 {amount} 个生命方块!"
+ check_location: "&e你的生命方块位置:"
+ location_item: "&7- {world} ({x}, {y}, {z})"
+ no_blocks: "&c你还没有生命方块!"
+
+ # 玩家状态
+ player:
+ eliminated: "&c玩家 {player} 已被淘汰!"
+ revived: "&a玩家 {player} 已被复活!"
+ already_eliminated: "&c玩家 {player} 已被淘汰,无法执行此操作!"
+ not_eliminated: "&c玩家 {player} 未被淘汰!"
+
+ # 错误消息
+ errors:
+ no_permission: "&c你没有权限使用此命令!"
+ player_not_found: "&c玩家 {player} 未找到!"
+ player_offline: "&c玩家 {player} 不在线!"
+ invalid_arguments: "&c参数无效!用法: {usage}"
+ command_disabled: "&c此命令已被禁用!"
+ world_not_found: "&c世界 {world} 未找到!"
+ cannot_generate_blocks: "&c无法生成生命方块:{reason}"
+ internal_error: "&c发生内部错误,请联系管理员!"
+
+ # 成功消息
+ success:
+ blocks_reset: "&a已重置你的生命方块!"
+ blocks_deleted: "&a已删除玩家 {player} 的生命方块!"
+ config_reloaded: "&a配置已重载!"
+ player_revived: "&a玩家 {player} 已复活!"
+
+# 命令消息
+commands:
+ setlifeblocks:
+ success: "&a已为你生成 {blocks} 个生命方块!"
+ already_has: "&c你已经有生命方块了!使用 /checklifeblocks 查看位置"
+ help: |
+ &6=== PlayerBlockLife 帮助 ===
+ &e/setlifeblocks &7- 设置你的生命方块
+ &e/setlifeblocks reset &7- 重置生命方块位置
+ &e/setlifeblocks other <玩家> &7- 为其他玩家设置(管理员)
+ &e/setlifeblocks help &7- 显示此帮助
+ usage: "&c用法: /setlifeblocks [reset|other|help]"
+
+ checklifeblocks:
+ success: "&e你的生命方块位置:"
+ no_blocks: "&c你还没有生命方块!"
+ usage: "&c用法: /checklifeblocks"
+
+ pblreload:
+ success: "&a配置已重载!"
+ usage: "&c用法: /pblreload"
+
+ pbldelete:
+ success: "&a已删除玩家 {player} 的生命方块!"
+ usage: "&c用法: /pbldelete <玩家>"
+
+ pblrevive:
+ success: "&a玩家 {player} 已复活!"
+ usage: "&c用法: /pblrevive [玩家]"
+
+ pblstats:
+ title: "&6=== PlayerBlockLife 统计 ==="
+ online_players: "&e在线玩家: {count}"
+ total_blocks: "&e总生命方块: {count}"
+ eliminated_players: "&e已淘汰玩家: {count}"
+ usage: "&c用法: /pblstats"
+
+# 广播消息
+broadcast:
+ block_destroyed: "&6{breaker} &7破坏了 &c{owner} &7的生命方块!"
+ player_eliminated: "&c☠ {player} &7的生命方块已被挖光,已被淘汰!"
+ player_revived: "&a✨ {player} &7已被复活!"
+
+# 变量说明
+# {player} - 玩家名称
+# {breaker} - 破坏者名称
+# {owner} - 方块所有者名称
+# {amount} - 方块数量
+# {remaining} - 剩余方块数量
+# {total} - 总方块数量
+# {x}, {y}, {z} - 坐标
+# {world} - 世界名称
+# {version} - 插件版本
+# {error} - 错误信息
+# {reason} - 原因
+# {count} - 计数
+# {usage} - 命令用法
\ No newline at end of file
diff --git a/build/resources/main/plugin.yml b/build/resources/main/plugin.yml
new file mode 100644
index 0000000..42218a3
--- /dev/null
+++ b/build/resources/main/plugin.yml
@@ -0,0 +1,84 @@
+name: PlayerBlockLife
+version: 2.2.0-1.20.4
+main: com.playerblocklife.PlayerBlockLife
+api-version: 1.20
+author: xiaobai
+description: PBL生存游戏模式 - 独特的生存竞技游戏
+website: https://github.com/yourname/PlayerBlockLife
+prefix: PlayerBlockLife
+
+commands:
+ setlifeblocks:
+ description: 设置你的生命方块(使用你的皮肤)
+ usage: |
+ / - 设置你的生命方块
+ / help - 显示帮助
+ / reset - 重置生命方块位置
+ / other <玩家> - 为其他玩家设置(管理员)
+ aliases: [sbl, lifeblocks, setblocks]
+ permission: playerblocklife.set
+ permission-message: "§c你没有权限使用此命令!"
+
+ checklifeblocks:
+ description: 查看你的生命方块位置
+ usage: /
+ aliases: [cbl, checklife, myblocks]
+ permission: playerblocklife.check
+ permission-message: "§c你没有权限使用此命令!"
+
+ pbl:
+ description: PBL 主要控制命令
+ usage: |
+ / start [时间(分钟)] - 开始游戏
+ / rstgm - 重置游戏
+ aliases: [pblgame, pblcontrol]
+ permission: playerblocklife.admin
+ permission-message: "§c你没有权限使用此命令!"
+
+ pblreload:
+ description: 重载插件配置
+ usage: /
+ aliases: [pblr]
+ permission: playerblocklife.admin
+ permission-message: "§c你没有权限使用此命令!"
+
+ pbldelete:
+ description: 删除指定玩家的生命方块
+ usage: / <玩家>
+ aliases: [pbldel, deleteblocks]
+ permission: playerblocklife.admin
+ permission-message: "§c你没有权限使用此命令!"
+
+ pblrevive:
+ description: 复活被淘汰的玩家
+ usage: / [玩家]
+ aliases: [revive]
+ permission: playerblocklife.admin
+ permission-message: "§c你没有权限使用此命令!"
+
+ pblstats:
+ description: 查看插件统计信息
+ usage: /
+ permission: playerblocklife.admin
+ permission-message: "§c你没有权限使用此命令!"
+
+permissions:
+ playerblocklife.*:
+ description: 所有 PBL 权限
+ children:
+ playerblocklife.set: true
+ playerblocklife.check: true
+ playerblocklife.admin: true
+ default: op
+
+ playerblocklife.set:
+ description: 允许设置生命方块
+ default: true
+
+ playerblocklife.check:
+ description: 允许查看生命方块
+ default: true
+
+ playerblocklife.admin:
+ description: 管理员权限
+ default: op
\ No newline at end of file
diff --git a/build/tmp/compileJava/previous-compilation-data.bin b/build/tmp/compileJava/previous-compilation-data.bin
new file mode 100644
index 0000000..a681249
Binary files /dev/null and b/build/tmp/compileJava/previous-compilation-data.bin differ
diff --git a/build/tmp/jar/MANIFEST.MF b/build/tmp/jar/MANIFEST.MF
new file mode 100644
index 0000000..4561731
--- /dev/null
+++ b/build/tmp/jar/MANIFEST.MF
@@ -0,0 +1,4 @@
+Manifest-Version: 1.0
+Implementation-Version: 2.2.0-alpha-1.20.4
+Main-Class: com.playerblocklife.PlayerBlockLife
+
diff --git a/build/tmp/javadoc/javadoc.options b/build/tmp/javadoc/javadoc.options
new file mode 100644
index 0000000..226479a
--- /dev/null
+++ b/build/tmp/javadoc/javadoc.options
@@ -0,0 +1,20 @@
+-classpath 'C:\\Users\\xiaobai\\IdeaProjects\\1\\PlayerBlockLife\\build\\classes\\java\\main;C:\\Users\\xiaobai\\IdeaProjects\\1\\PlayerBlockLife\\build\\resources\\main;C:\\Users\\xiaobai\\.gradle\\caches\\modules-2\\files-2.1\\io.papermc.paper\\paper-api\\1.20.4-R0.1-SNAPSHOT\\8b9cca4e56ef91855140e918ec50cb4ed5189d81\\paper-api-1.20.4-R0.1-SNAPSHOT.jar;C:\\Users\\xiaobai\\.gradle\\caches\\modules-2\\files-2.1\\net.md-5\\bungeecord-chat\\1.20-R0.2-deprecated+build.18\\5035a741a44c35d9284251413050dc6470c09978\\bungeecord-chat-1.20-R0.2-deprecated+build.18.jar;C:\\Users\\xiaobai\\.gradle\\caches\\modules-2\\files-2.1\\net.kyori\\adventure-text-logger-slf4j\\4.16.0\\a2e7eadb8d26481fb3da619a761cf3a47a1da43f\\adventure-text-logger-slf4j-4.16.0.jar;C:\\Users\\xiaobai\\.gradle\\caches\\modules-2\\files-2.1\\net.kyori\\adventure-text-minimessage\\4.16.0\\7f319fbd29ecd725eeebb0cb3dca2195fbb65c07\\adventure-text-minimessage-4.16.0.jar;C:\\Users\\xiaobai\\.gradle\\caches\\modules-2\\files-2.1\\net.kyori\\adventure-text-serializer-legacy\\4.16.0\\21b9450c659146ea4ac6ef3555b5ea1008566b69\\adventure-text-serializer-legacy-4.16.0.jar;C:\\Users\\xiaobai\\.gradle\\caches\\modules-2\\files-2.1\\net.kyori\\adventure-text-serializer-plain\\4.16.0\\4d5e8b73aac1e5e17dd24a75edd8fd077098e18c\\adventure-text-serializer-plain-4.16.0.jar;C:\\Users\\xiaobai\\.gradle\\caches\\modules-2\\files-2.1\\net.kyori\\adventure-text-serializer-json\\4.16.0\\a78b40dd5b41ae2468d6299c630afdf2ac6d7c64\\adventure-text-serializer-json-4.16.0.jar;C:\\Users\\xiaobai\\.gradle\\caches\\modules-2\\files-2.1\\net.kyori\\adventure-api\\4.16.0\\cb966704b813d30d4ee9f0b97167b4063a249c34\\adventure-api-4.16.0.jar;C:\\Users\\xiaobai\\.gradle\\caches\\modules-2\\files-2.1\\net.kyori\\adventure-key\\4.16.0\\b695c40a7d2fd658246de78ea428e8f8dc7ffd2d\\adventure-key-4.16.0.jar;C:\\Users\\xiaobai\\.gradle\\caches\\modules-2\\files-2.1\\net.kyori\\adventure-text-serializer-gson\\4.16.0\\5650ed18040e070aa05855ebcb890e6e1e36ee0e\\adventure-text-serializer-gson-4.16.0.jar;C:\\Users\\xiaobai\\.gradle\\caches\\modules-2\\files-2.1\\com.google.code.gson\\gson\\2.10.1\\b3add478d4382b78ea20b1671390a858002feb6c\\gson-2.10.1.jar;C:\\Users\\xiaobai\\.gradle\\caches\\modules-2\\files-2.1\\org.yaml\\snakeyaml\\2.2\\3af797a25458550a16bf89acc8e4ab2b7f2bfce0\\snakeyaml-2.2.jar;C:\\Users\\xiaobai\\.gradle\\caches\\modules-2\\files-2.1\\com.google.guava\\guava\\32.1.2-jre\\5e64ec7e056456bef3a4bc4c6fdaef71e8ab6318\\guava-32.1.2-jre.jar;C:\\Users\\xiaobai\\.gradle\\caches\\modules-2\\files-2.1\\org.joml\\joml\\1.10.5\\22566d58af70ad3d72308bab63b8339906deb649\\joml-1.10.5.jar;C:\\Users\\xiaobai\\.gradle\\caches\\modules-2\\files-2.1\\com.googlecode.json-simple\\json-simple\\1.1.1\\c9ad4a0850ab676c5c64461a05ca524cdfff59f1\\json-simple-1.1.1.jar;C:\\Users\\xiaobai\\.gradle\\caches\\modules-2\\files-2.1\\it.unimi.dsi\\fastutil\\8.5.6\\76f95700418a68fbc4ac050525261f05dc681ca1\\fastutil-8.5.6.jar;C:\\Users\\xiaobai\\.gradle\\caches\\modules-2\\files-2.1\\org.apache.logging.log4j\\log4j-api\\2.17.1\\d771af8e336e372fb5399c99edabe0919aeaf5b2\\log4j-api-2.17.1.jar;C:\\Users\\xiaobai\\.gradle\\caches\\modules-2\\files-2.1\\org.apache.maven\\maven-resolver-provider\\3.9.6\\848c45d334f6cc5c8dd602b0e58fd4482964eddc\\maven-resolver-provider-3.9.6.jar;C:\\Users\\xiaobai\\.gradle\\caches\\modules-2\\files-2.1\\org.apache.maven.resolver\\maven-resolver-impl\\1.9.18\\e928b128d1e52e6299f94431ce3df74647bc8c26\\maven-resolver-impl-1.9.18.jar;C:\\Users\\xiaobai\\.gradle\\caches\\modules-2\\files-2.1\\org.apache.maven.resolver\\maven-resolver-named-locks\\1.9.18\\31f948d89dcb3d9739e70d5e1000ebd68eb4405d\\maven-resolver-named-locks-1.9.18.jar;C:\\Users\\xiaobai\\.gradle\\caches\\modules-2\\files-2.1\\org.slf4j\\slf4j-api\\2.0.9\\7cf2726fdcfbc8610f9a71fb3ed639871f315340\\slf4j-api-2.0.9.jar;C:\\Users\\xiaobai\\.gradle\\caches\\modules-2\\files-2.1\\org.checkerframework\\checker-qual\\3.33.0\\de2b60b62da487644fc11f734e73c8b0b431238f\\checker-qual-3.33.0.jar;C:\\Users\\xiaobai\\.gradle\\caches\\modules-2\\files-2.1\\com.google.guava\\failureaccess\\1.0.1\\1dcf1de382a0bf95a3d8b0849546c88bac1292c9\\failureaccess-1.0.1.jar;C:\\Users\\xiaobai\\.gradle\\caches\\modules-2\\files-2.1\\com.google.guava\\listenablefuture\\9999.0-empty-to-avoid-conflict-with-guava\\b421526c5f297295adef1c886e5246c39d4ac629\\listenablefuture-9999.0-empty-to-avoid-conflict-with-guava.jar;C:\\Users\\xiaobai\\.gradle\\caches\\modules-2\\files-2.1\\com.google.code.findbugs\\jsr305\\3.0.2\\25ea2e8b0c338a877313bd4672d3fe056ea78f0d\\jsr305-3.0.2.jar;C:\\Users\\xiaobai\\.gradle\\caches\\modules-2\\files-2.1\\com.google.errorprone\\error_prone_annotations\\2.18.0\\89b684257096f548fa39a7df9fdaa409d4d4df91\\error_prone_annotations-2.18.0.jar;C:\\Users\\xiaobai\\.gradle\\caches\\modules-2\\files-2.1\\com.google.j2objc\\j2objc-annotations\\2.8\\c85270e307e7b822f1086b93689124b89768e273\\j2objc-annotations-2.8.jar;C:\\Users\\xiaobai\\.gradle\\caches\\modules-2\\files-2.1\\org.apache.maven\\maven-model-builder\\3.9.6\\983ce00d50a9f78ad1b805e21e4fd71807fa6ebf\\maven-model-builder-3.9.6.jar;C:\\Users\\xiaobai\\.gradle\\caches\\modules-2\\files-2.1\\org.apache.maven\\maven-model\\3.9.6\\ac9a1c8a8cfa36f3a5489837e653ec0cd530d576\\maven-model-3.9.6.jar;C:\\Users\\xiaobai\\.gradle\\caches\\modules-2\\files-2.1\\org.apache.maven\\maven-repository-metadata\\3.9.6\\497cda3149f3c661113f9a663e0270ce2566cc95\\maven-repository-metadata-3.9.6.jar;C:\\Users\\xiaobai\\.gradle\\caches\\modules-2\\files-2.1\\org.apache.maven.resolver\\maven-resolver-spi\\1.9.18\\7fa176b3353ef6d78d02db39e025f3c27a983158\\maven-resolver-spi-1.9.18.jar;C:\\Users\\xiaobai\\.gradle\\caches\\modules-2\\files-2.1\\org.apache.maven.resolver\\maven-resolver-util\\1.9.18\\5ae9406f188ae4a999c353fce3fd77273797a216\\maven-resolver-util-1.9.18.jar;C:\\Users\\xiaobai\\.gradle\\caches\\modules-2\\files-2.1\\org.apache.maven.resolver\\maven-resolver-api\\1.9.18\\cd5174d6e80175398debe4869d484169c0abbf8\\maven-resolver-api-1.9.18.jar;C:\\Users\\xiaobai\\.gradle\\caches\\modules-2\\files-2.1\\org.apache.maven\\maven-artifact\\3.9.6\\fb0979832c10c1a25d038a33ca862bef055fcdc8\\maven-artifact-3.9.6.jar;C:\\Users\\xiaobai\\.gradle\\caches\\modules-2\\files-2.1\\org.codehaus.plexus\\plexus-utils\\3.5.1\\c6bfb17c97ecc8863e88778ea301be742c62b06d\\plexus-utils-3.5.1.jar;C:\\Users\\xiaobai\\.gradle\\caches\\modules-2\\files-2.1\\javax.inject\\javax.inject\\1\\6975da39a7040257bd51d21a231b76c915872d38\\javax.inject-1.jar;C:\\Users\\xiaobai\\.gradle\\caches\\modules-2\\files-2.1\\net.kyori\\examination-string\\1.3.0\\6f34afef5c54ccce4996bc321abf77518b55b4bd\\examination-string-1.3.0.jar;C:\\Users\\xiaobai\\.gradle\\caches\\modules-2\\files-2.1\\net.kyori\\examination-api\\1.3.0\\8a2d185275307f1e2ef2adf7152b9a0d1d44c30b\\examination-api-1.3.0.jar;C:\\Users\\xiaobai\\.gradle\\caches\\modules-2\\files-2.1\\net.kyori\\option\\1.0.0\\59c5ae54c47ae294322ad979eaf7cdcde7ad0646\\option-1.0.0.jar;C:\\Users\\xiaobai\\.gradle\\caches\\modules-2\\files-2.1\\org.jetbrains\\annotations\\24.1.0\\7af6a669488450c4a07c2c3254e2151df42d7d04\\annotations-24.1.0.jar;C:\\Users\\xiaobai\\.gradle\\caches\\modules-2\\files-2.1\\org.codehaus.plexus\\plexus-interpolation\\1.26\\25b919c664b79795ccde0ede5cee0fd68b544197\\plexus-interpolation-1.26.jar;C:\\Users\\xiaobai\\.gradle\\caches\\modules-2\\files-2.1\\org.apache.maven\\maven-builder-support\\3.9.6\\bcfc9d8175eaba21111edf21e0355a8523461abc\\maven-builder-support-3.9.6.jar;C:\\Users\\xiaobai\\.gradle\\caches\\modules-2\\files-2.1\\org.eclipse.sisu\\org.eclipse.sisu.inject\\0.9.0.M2\\5ace70e1ea696d156f5034a42a615df13a52003a\\org.eclipse.sisu.inject-0.9.0.M2.jar;C:\\Users\\xiaobai\\.gradle\\caches\\modules-2\\files-2.1\\org.apache.commons\\commons-lang3\\3.12.0\\c6842c86792ff03b9f1d1fe2aab8dc23aa6c6f0e\\commons-lang3-3.12.0.jar'
+-d 'C:\\Users\\xiaobai\\IdeaProjects\\1\\PlayerBlockLife\\build\\docs\\javadoc'
+-doctitle 'PlayerBlockLife 2.2.0-alpha-1.20.4 API'
+-notimestamp
+-quiet
+-windowtitle 'PlayerBlockLife 2.2.0-alpha-1.20.4 API'
+'C:\\Users\\xiaobai\\IdeaProjects\\1\\PlayerBlockLife\\src\\main\\java\\com\\playerblocklife\\AdminCommands.java'
+'C:\\Users\\xiaobai\\IdeaProjects\\1\\PlayerBlockLife\\src\\main\\java\\com\\playerblocklife\\BlockBreakListener.java'
+'C:\\Users\\xiaobai\\IdeaProjects\\1\\PlayerBlockLife\\src\\main\\java\\com\\playerblocklife\\CheckLifeBlocksCommand.java'
+'C:\\Users\\xiaobai\\IdeaProjects\\1\\PlayerBlockLife\\src\\main\\java\\com\\playerblocklife\\ConfigManager.java'
+'C:\\Users\\xiaobai\\IdeaProjects\\1\\PlayerBlockLife\\src\\main\\java\\com\\playerblocklife\\GameStateManager.java'
+'C:\\Users\\xiaobai\\IdeaProjects\\1\\PlayerBlockLife\\src\\main\\java\\com\\playerblocklife\\LifeSystem.java'
+'C:\\Users\\xiaobai\\IdeaProjects\\1\\PlayerBlockLife\\src\\main\\java\\com\\playerblocklife\\MessageManager.java'
+'C:\\Users\\xiaobai\\IdeaProjects\\1\\PlayerBlockLife\\src\\main\\java\\com\\playerblocklife\\PBLCommands.java'
+'C:\\Users\\xiaobai\\IdeaProjects\\1\\PlayerBlockLife\\src\\main\\java\\com\\playerblocklife\\PlayerBlockLife.java'
+'C:\\Users\\xiaobai\\IdeaProjects\\1\\PlayerBlockLife\\src\\main\\java\\com\\playerblocklife\\PlayerBlockManager.java'
+'C:\\Users\\xiaobai\\IdeaProjects\\1\\PlayerBlockLife\\src\\main\\java\\com\\playerblocklife\\PlayerJoinListener.java'
+'C:\\Users\\xiaobai\\IdeaProjects\\1\\PlayerBlockLife\\src\\main\\java\\com\\playerblocklife\\PlayerQuitListener.java'
+'C:\\Users\\xiaobai\\IdeaProjects\\1\\PlayerBlockLife\\src\\main\\java\\com\\playerblocklife\\SetLifeBlocksCommand.java'
+'C:\\Users\\xiaobai\\IdeaProjects\\1\\PlayerBlockLife\\src\\main\\java\\com\\playerblocklife\\SkinManager.java'
diff --git a/build/tmp/shadowJar/MANIFEST.MF b/build/tmp/shadowJar/MANIFEST.MF
new file mode 100644
index 0000000..5425c53
--- /dev/null
+++ b/build/tmp/shadowJar/MANIFEST.MF
@@ -0,0 +1,4 @@
+Manifest-Version: 1.0
+Implementation-Version: 2.2.0-1.20.4
+Main-Class: com.playerblocklife.PlayerBlockLife
+
diff --git a/gradle.properties b/gradle.properties
index 2f5e064..04c8154 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -5,5 +5,5 @@ org.gradle.caching=true
org.gradle.daemon=true
# ????
-pluginVersion=2.1.0-1.20.4
+pluginVersion=2.2.0-1.20.4
mcVersion=1.20.4
\ No newline at end of file
diff --git a/javadoc/allclasses-index.html b/javadoc/allclasses-index.html
index 1c3d950..912320a 100644
--- a/javadoc/allclasses-index.html
+++ b/javadoc/allclasses-index.html
@@ -1,7 +1,7 @@
-
+
所有类和接口
diff --git a/javadoc/allpackages-index.html b/javadoc/allpackages-index.html
index db123d2..c3a4ecd 100644
--- a/javadoc/allpackages-index.html
+++ b/javadoc/allpackages-index.html
@@ -1,7 +1,7 @@
-
+
所有程序包
diff --git a/javadoc/com/playerblocklife/AdminCommands.html b/javadoc/com/playerblocklife/AdminCommands.html
index 7329da8..aa2a5f6 100644
--- a/javadoc/com/playerblocklife/AdminCommands.html
+++ b/javadoc/com/playerblocklife/AdminCommands.html
@@ -1,7 +1,7 @@
-
+
AdminCommands
diff --git a/javadoc/com/playerblocklife/BlockBreakListener.html b/javadoc/com/playerblocklife/BlockBreakListener.html
index cd8cb8a..6dd0d39 100644
--- a/javadoc/com/playerblocklife/BlockBreakListener.html
+++ b/javadoc/com/playerblocklife/BlockBreakListener.html
@@ -1,7 +1,7 @@
-
+
BlockBreakListener
diff --git a/javadoc/com/playerblocklife/CheckLifeBlocksCommand.html b/javadoc/com/playerblocklife/CheckLifeBlocksCommand.html
index e21abf9..6a18a6f 100644
--- a/javadoc/com/playerblocklife/CheckLifeBlocksCommand.html
+++ b/javadoc/com/playerblocklife/CheckLifeBlocksCommand.html
@@ -1,7 +1,7 @@
-
+
CheckLifeBlocksCommand
diff --git a/javadoc/com/playerblocklife/ConfigManager.html b/javadoc/com/playerblocklife/ConfigManager.html
index fecb05b..1bba39a 100644
--- a/javadoc/com/playerblocklife/ConfigManager.html
+++ b/javadoc/com/playerblocklife/ConfigManager.html
@@ -1,7 +1,7 @@
-
+
ConfigManager
@@ -481,9 +481,14 @@ loadScripts(document, 'script');
皮肤来源配置值
另请参阅:
-
diff --git a/javadoc/com/playerblocklife/LifeSystem.html b/javadoc/com/playerblocklife/LifeSystem.html
index 5e36b6a..aaab560 100644
--- a/javadoc/com/playerblocklife/LifeSystem.html
+++ b/javadoc/com/playerblocklife/LifeSystem.html
@@ -1,7 +1,7 @@
-
+
LifeSystem
diff --git a/javadoc/com/playerblocklife/MessageManager.html b/javadoc/com/playerblocklife/MessageManager.html
index fea3f0f..29c8b3e 100644
--- a/javadoc/com/playerblocklife/MessageManager.html
+++ b/javadoc/com/playerblocklife/MessageManager.html
@@ -1,7 +1,7 @@
-
+
MessageManager
diff --git a/javadoc/com/playerblocklife/PlayerBlockLife.html b/javadoc/com/playerblocklife/PlayerBlockLife.html
index f4e159e..d5dfe54 100644
--- a/javadoc/com/playerblocklife/PlayerBlockLife.html
+++ b/javadoc/com/playerblocklife/PlayerBlockLife.html
@@ -1,7 +1,7 @@
-
+
PlayerBlockLife
diff --git a/javadoc/com/playerblocklife/PlayerBlockManager.html b/javadoc/com/playerblocklife/PlayerBlockManager.html
index b34a688..fe60cff 100644
--- a/javadoc/com/playerblocklife/PlayerBlockManager.html
+++ b/javadoc/com/playerblocklife/PlayerBlockManager.html
@@ -1,7 +1,7 @@
-
+
PlayerBlockManager
diff --git a/javadoc/com/playerblocklife/PlayerJoinListener.html b/javadoc/com/playerblocklife/PlayerJoinListener.html
index 6919511..0fa424b 100644
--- a/javadoc/com/playerblocklife/PlayerJoinListener.html
+++ b/javadoc/com/playerblocklife/PlayerJoinListener.html
@@ -1,7 +1,7 @@
-
+
PlayerJoinListener
diff --git a/javadoc/com/playerblocklife/PlayerQuitListener.html b/javadoc/com/playerblocklife/PlayerQuitListener.html
index 55a7a78..405ac67 100644
--- a/javadoc/com/playerblocklife/PlayerQuitListener.html
+++ b/javadoc/com/playerblocklife/PlayerQuitListener.html
@@ -1,7 +1,7 @@
-
+
PlayerQuitListener
diff --git a/javadoc/com/playerblocklife/SetLifeBlocksCommand.html b/javadoc/com/playerblocklife/SetLifeBlocksCommand.html
index 336d3ee..719d1ab 100644
--- a/javadoc/com/playerblocklife/SetLifeBlocksCommand.html
+++ b/javadoc/com/playerblocklife/SetLifeBlocksCommand.html
@@ -1,7 +1,7 @@
-
+
SetLifeBlocksCommand
diff --git a/javadoc/com/playerblocklife/SkinManager.html b/javadoc/com/playerblocklife/SkinManager.html
index 80185d1..a1ba6d4 100644
--- a/javadoc/com/playerblocklife/SkinManager.html
+++ b/javadoc/com/playerblocklife/SkinManager.html
@@ -1,7 +1,7 @@
-
+
SkinManager
diff --git a/javadoc/com/playerblocklife/class-use/AdminCommands.html b/javadoc/com/playerblocklife/class-use/AdminCommands.html
index 5a85525..cb7f586 100644
--- a/javadoc/com/playerblocklife/class-use/AdminCommands.html
+++ b/javadoc/com/playerblocklife/class-use/AdminCommands.html
@@ -1,7 +1,7 @@
-
+
类 com.playerblocklife.AdminCommands的使用
diff --git a/javadoc/com/playerblocklife/class-use/BlockBreakListener.html b/javadoc/com/playerblocklife/class-use/BlockBreakListener.html
index 60c7771..925e6c1 100644
--- a/javadoc/com/playerblocklife/class-use/BlockBreakListener.html
+++ b/javadoc/com/playerblocklife/class-use/BlockBreakListener.html
@@ -1,7 +1,7 @@
-
+
类 com.playerblocklife.BlockBreakListener的使用
diff --git a/javadoc/com/playerblocklife/class-use/CheckLifeBlocksCommand.html b/javadoc/com/playerblocklife/class-use/CheckLifeBlocksCommand.html
index 041eea8..ef2b6c6 100644
--- a/javadoc/com/playerblocklife/class-use/CheckLifeBlocksCommand.html
+++ b/javadoc/com/playerblocklife/class-use/CheckLifeBlocksCommand.html
@@ -1,7 +1,7 @@
-
+
类 com.playerblocklife.CheckLifeBlocksCommand的使用
diff --git a/javadoc/com/playerblocklife/class-use/ConfigManager.html b/javadoc/com/playerblocklife/class-use/ConfigManager.html
index 8d11e6f..1d2589f 100644
--- a/javadoc/com/playerblocklife/class-use/ConfigManager.html
+++ b/javadoc/com/playerblocklife/class-use/ConfigManager.html
@@ -1,7 +1,7 @@
-
+
类 com.playerblocklife.ConfigManager的使用
diff --git a/javadoc/com/playerblocklife/class-use/LifeSystem.html b/javadoc/com/playerblocklife/class-use/LifeSystem.html
index e1724d4..a07aca2 100644
--- a/javadoc/com/playerblocklife/class-use/LifeSystem.html
+++ b/javadoc/com/playerblocklife/class-use/LifeSystem.html
@@ -1,7 +1,7 @@
-
+
类 com.playerblocklife.LifeSystem的使用
diff --git a/javadoc/com/playerblocklife/class-use/MessageManager.html b/javadoc/com/playerblocklife/class-use/MessageManager.html
index 3518ed8..53763c3 100644
--- a/javadoc/com/playerblocklife/class-use/MessageManager.html
+++ b/javadoc/com/playerblocklife/class-use/MessageManager.html
@@ -1,7 +1,7 @@
-
+
类 com.playerblocklife.MessageManager的使用
diff --git a/javadoc/com/playerblocklife/class-use/PlayerBlockLife.html b/javadoc/com/playerblocklife/class-use/PlayerBlockLife.html
index 27b89f6..bf74459 100644
--- a/javadoc/com/playerblocklife/class-use/PlayerBlockLife.html
+++ b/javadoc/com/playerblocklife/class-use/PlayerBlockLife.html
@@ -1,7 +1,7 @@
-
+
类 com.playerblocklife.PlayerBlockLife的使用
diff --git a/javadoc/com/playerblocklife/class-use/PlayerBlockManager.html b/javadoc/com/playerblocklife/class-use/PlayerBlockManager.html
index 192e31e..a8de8c8 100644
--- a/javadoc/com/playerblocklife/class-use/PlayerBlockManager.html
+++ b/javadoc/com/playerblocklife/class-use/PlayerBlockManager.html
@@ -1,7 +1,7 @@
-
+
类 com.playerblocklife.PlayerBlockManager的使用
diff --git a/javadoc/com/playerblocklife/class-use/PlayerJoinListener.html b/javadoc/com/playerblocklife/class-use/PlayerJoinListener.html
index 90f3959..8deb4d5 100644
--- a/javadoc/com/playerblocklife/class-use/PlayerJoinListener.html
+++ b/javadoc/com/playerblocklife/class-use/PlayerJoinListener.html
@@ -1,7 +1,7 @@
-
+
类 com.playerblocklife.PlayerJoinListener的使用
diff --git a/javadoc/com/playerblocklife/class-use/PlayerQuitListener.html b/javadoc/com/playerblocklife/class-use/PlayerQuitListener.html
index 4d8bf8c..10b5e4b 100644
--- a/javadoc/com/playerblocklife/class-use/PlayerQuitListener.html
+++ b/javadoc/com/playerblocklife/class-use/PlayerQuitListener.html
@@ -1,7 +1,7 @@
-
+
类 com.playerblocklife.PlayerQuitListener的使用
diff --git a/javadoc/com/playerblocklife/class-use/SetLifeBlocksCommand.html b/javadoc/com/playerblocklife/class-use/SetLifeBlocksCommand.html
index da875d6..612cca3 100644
--- a/javadoc/com/playerblocklife/class-use/SetLifeBlocksCommand.html
+++ b/javadoc/com/playerblocklife/class-use/SetLifeBlocksCommand.html
@@ -1,7 +1,7 @@
-
+
类 com.playerblocklife.SetLifeBlocksCommand的使用
diff --git a/javadoc/com/playerblocklife/class-use/SkinManager.html b/javadoc/com/playerblocklife/class-use/SkinManager.html
index 644d342..2555319 100644
--- a/javadoc/com/playerblocklife/class-use/SkinManager.html
+++ b/javadoc/com/playerblocklife/class-use/SkinManager.html
@@ -1,7 +1,7 @@
-
+
类 com.playerblocklife.SkinManager的使用
diff --git a/javadoc/com/playerblocklife/package-summary.html b/javadoc/com/playerblocklife/package-summary.html
index 3a261ca..ce3fe78 100644
--- a/javadoc/com/playerblocklife/package-summary.html
+++ b/javadoc/com/playerblocklife/package-summary.html
@@ -1,7 +1,7 @@
-
+
com.playerblocklife
diff --git a/javadoc/com/playerblocklife/package-tree.html b/javadoc/com/playerblocklife/package-tree.html
index d5dd9d9..8e5b061 100644
--- a/javadoc/com/playerblocklife/package-tree.html
+++ b/javadoc/com/playerblocklife/package-tree.html
@@ -1,7 +1,7 @@
-
+
com.playerblocklife 类分层结构
diff --git a/javadoc/com/playerblocklife/package-use.html b/javadoc/com/playerblocklife/package-use.html
index b8234ba..ef6dc0b 100644
--- a/javadoc/com/playerblocklife/package-use.html
+++ b/javadoc/com/playerblocklife/package-use.html
@@ -1,7 +1,7 @@
-
+
程序包 com.playerblocklife的使用
diff --git a/javadoc/help-doc.html b/javadoc/help-doc.html
index 14be97b..7206a09 100644
--- a/javadoc/help-doc.html
+++ b/javadoc/help-doc.html
@@ -1,7 +1,7 @@
-
+
API 帮助
diff --git a/javadoc/index-files/index-1.html b/javadoc/index-files/index-1.html
index 09ca566..940b31f 100644
--- a/javadoc/index-files/index-1.html
+++ b/javadoc/index-files/index-1.html
@@ -1,7 +1,7 @@
-
+
A - 索引
diff --git a/javadoc/index-files/index-10.html b/javadoc/index-files/index-10.html
index df27d9c..2016708 100644
--- a/javadoc/index-files/index-10.html
+++ b/javadoc/index-files/index-10.html
@@ -1,7 +1,7 @@
-
+
P - 索引
diff --git a/javadoc/index-files/index-11.html b/javadoc/index-files/index-11.html
index b4612cf..a14580e 100644
--- a/javadoc/index-files/index-11.html
+++ b/javadoc/index-files/index-11.html
@@ -1,7 +1,7 @@
-
+
R - 索引
diff --git a/javadoc/index-files/index-12.html b/javadoc/index-files/index-12.html
index 4044b6f..830fcb3 100644
--- a/javadoc/index-files/index-12.html
+++ b/javadoc/index-files/index-12.html
@@ -1,7 +1,7 @@
-
+
S - 索引
diff --git a/javadoc/index-files/index-13.html b/javadoc/index-files/index-13.html
index 00ef5aa..4b3c581 100644
--- a/javadoc/index-files/index-13.html
+++ b/javadoc/index-files/index-13.html
@@ -1,7 +1,7 @@
-
+
U - 索引
diff --git a/javadoc/index-files/index-2.html b/javadoc/index-files/index-2.html
index 1bb1acb..68e6ada 100644
--- a/javadoc/index-files/index-2.html
+++ b/javadoc/index-files/index-2.html
@@ -1,7 +1,7 @@
-
+
B - 索引
diff --git a/javadoc/index-files/index-3.html b/javadoc/index-files/index-3.html
index 3196d26..f41618c 100644
--- a/javadoc/index-files/index-3.html
+++ b/javadoc/index-files/index-3.html
@@ -1,7 +1,7 @@
-
+
C - 索引
diff --git a/javadoc/index-files/index-4.html b/javadoc/index-files/index-4.html
index ebba9ab..ce52cda 100644
--- a/javadoc/index-files/index-4.html
+++ b/javadoc/index-files/index-4.html
@@ -1,7 +1,7 @@
-
+
G - 索引
diff --git a/javadoc/index-files/index-5.html b/javadoc/index-files/index-5.html
index 0a001a5..4a898ca 100644
--- a/javadoc/index-files/index-5.html
+++ b/javadoc/index-files/index-5.html
@@ -1,7 +1,7 @@
-
+
H - 索引
diff --git a/javadoc/index-files/index-6.html b/javadoc/index-files/index-6.html
index a3d60df..ded9d09 100644
--- a/javadoc/index-files/index-6.html
+++ b/javadoc/index-files/index-6.html
@@ -1,7 +1,7 @@
-
+
I - 索引
diff --git a/javadoc/index-files/index-7.html b/javadoc/index-files/index-7.html
index e9a0c54..50eff6b 100644
--- a/javadoc/index-files/index-7.html
+++ b/javadoc/index-files/index-7.html
@@ -1,7 +1,7 @@
-
+
L - 索引
diff --git a/javadoc/index-files/index-8.html b/javadoc/index-files/index-8.html
index 36252e4..48df9b5 100644
--- a/javadoc/index-files/index-8.html
+++ b/javadoc/index-files/index-8.html
@@ -1,7 +1,7 @@
-
+
M - 索引
diff --git a/javadoc/index-files/index-9.html b/javadoc/index-files/index-9.html
index d9d2837..1f56e7a 100644
--- a/javadoc/index-files/index-9.html
+++ b/javadoc/index-files/index-9.html
@@ -1,7 +1,7 @@
-
+
O - 索引
diff --git a/javadoc/index.html b/javadoc/index.html
index 78069b8..486e52e 100644
--- a/javadoc/index.html
+++ b/javadoc/index.html
@@ -1,7 +1,7 @@
-
+
生成的文档 (无标题)
diff --git a/javadoc/overview-tree.html b/javadoc/overview-tree.html
index 887d30e..72c1387 100644
--- a/javadoc/overview-tree.html
+++ b/javadoc/overview-tree.html
@@ -1,7 +1,7 @@
-
+
类分层结构
diff --git a/javadoc/search.html b/javadoc/search.html
index dd64a72..aa36243 100644
--- a/javadoc/search.html
+++ b/javadoc/search.html
@@ -1,7 +1,7 @@
-
+
搜索
diff --git a/src/main/java/com/playerblocklife/AdminCommands.java b/src/main/java/com/playerblocklife/AdminCommands.java
index 9a7df76..ddea23e 100644
--- a/src/main/java/com/playerblocklife/AdminCommands.java
+++ b/src/main/java/com/playerblocklife/AdminCommands.java
@@ -1,6 +1,7 @@
package com.playerblocklife;
import org.bukkit.Bukkit;
+import org.bukkit.ChatColor;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
@@ -8,77 +9,72 @@ import org.bukkit.entity.Player;
import java.util.UUID;
+/**
+ * AdminCommands - PlayerBlockLife游戏模式的管理员命令处理器
+ *
+ * 处理插件的传统管理员命令,适配新的PlayerBlockLife游戏模式:
+ *
+ * /pblreload - 重载插件配置
+ * /pbldelete - 删除指定玩家的生命方块
+ * /pblrevive - 复活被淘汰的玩家
+ * /pblstats - 显示PlayerBlockLife游戏统计信息
+ *
+ *
+ * 这些命令在新模式下会与GameStateManager交互,以正确管理游戏状态。
+ *
+ * @author xiaobai
+ * @version 2.2.0-1.20.4
+ * @since 1.0.0
+ */
public class AdminCommands implements CommandExecutor {
private final PlayerBlockLife plugin;
+ /**
+ * 构造一个新的管理员命令执行器
+ *
+ * @param plugin 插件主类实例
+ */
public AdminCommands(PlayerBlockLife plugin) {
this.plugin = plugin;
}
@Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
- MessageManager msgManager = plugin.getMessageManager();
- ConfigManager config = plugin.getConfigManager();
-
String commandName = command.getName().toLowerCase();
- // 检查命令是否启用
- if (!config.isCommandEnabled(commandName)) {
- String message = msgManager.getMessage("game.errors.command_disabled",
- "&c此命令已被禁用!");
- sender.sendMessage(message);
- return true;
- }
-
- // 检查管理员权限和配置
- if (!sender.hasPermission("playerblocklife.admin") || config.isAdminOnly(commandName)) {
- String message = msgManager.getMessage("game.errors.no_permission",
- "&c你没有权限使用此命令!");
- sender.sendMessage(message);
+ if (!sender.hasPermission("playerblocklife.admin")) {
+ sender.sendMessage(ChatColor.RED + "你没有权限使用此命令!");
return true;
}
if (commandName.equals("pblreload")) {
- // 调用插件的完整重载方法
+ // 重载配置
plugin.reloadPluginConfig();
- String message = msgManager.getCommandMessage("pblreload", "success",
- "&a配置已重载!");
- sender.sendMessage(message);
+ sender.sendMessage(ChatColor.GREEN + "配置已重载!");
return true;
}
if (commandName.equals("pbldelete")) {
if (args.length < 1) {
- String usage = msgManager.getCommandMessage("pbldelete", "usage",
- "&c用法: /pbldelete <玩家>");
- sender.sendMessage(usage);
+ sender.sendMessage(ChatColor.RED + "用法: /pbldelete <玩家>");
return true;
}
String targetName = args[0];
Player target = Bukkit.getPlayer(targetName);
- UUID targetId;
- if (target != null) {
- targetId = target.getUniqueId();
- } else {
- // 尝试从离线玩家获取UUID
- try {
- targetId = Bukkit.getOfflinePlayer(targetName).getUniqueId();
- } catch (Exception e) {
- String message = msgManager.getMessage("game.errors.player_not_found",
- "&c找不到玩家: {player}");
- message = message.replace("{player}", targetName);
- sender.sendMessage(message);
- return true;
- }
+ if (target == null) {
+ sender.sendMessage(ChatColor.RED + "找不到玩家: " + targetName);
+ return true;
}
- plugin.getBlockManager().clearPlayerBlocks(targetId);
- String message = msgManager.getCommandMessage("pbldelete", "success",
- "&a已删除玩家 {player} 的生命方块!");
- message = message.replace("{player}", targetName);
- sender.sendMessage(message);
+ // 在新模式下,我们使用GameStateManager来清除玩家方块
+ // 清除玩家的方块数据
+ UUID targetId = target.getUniqueId();
+ plugin.getGameStateManager().clearPlayerBlocks(targetId);
+
+ sender.sendMessage(ChatColor.GREEN + "已删除玩家 " + targetName + " 的生命方块!");
+ target.sendMessage(ChatColor.YELLOW + "你的生命方块已被管理员清除。");
return true;
}
@@ -87,87 +83,98 @@ public class AdminCommands implements CommandExecutor {
if (args.length < 1) {
if (!(sender instanceof Player)) {
- String usage = msgManager.getCommandMessage("pblrevive", "usage",
- "&c用法: /pblrevive [玩家]");
- sender.sendMessage(usage);
+ sender.sendMessage(ChatColor.RED + "用法: /pblrevive <玩家>");
return true;
}
target = (Player) sender;
} else {
target = Bukkit.getPlayer(args[0]);
if (target == null) {
- String message = msgManager.getMessage("game.errors.player_offline",
- "&c玩家 {player} 不在线!");
- message = message.replace("{player}", args[0]);
- sender.sendMessage(message);
+ sender.sendMessage(ChatColor.RED + "玩家 " + args[0] + " 不在线!");
return true;
}
}
- if (plugin.getLifeSystem() != null) {
- plugin.getLifeSystem().revivePlayer(target);
- String message = msgManager.getCommandMessage("pblrevive", "success",
- "&a玩家 {player} 已复活!");
- message = message.replace("{player}", target.getName());
- sender.sendMessage(message);
+ // 在新模式下,复活玩家需要将其从淘汰列表中移除并恢复生存模式
+ UUID targetId = target.getUniqueId();
+ if (plugin.getGameStateManager().getRemainingBlocks(targetId) <= 0) {
+ // 如果玩家已被淘汰,需要将其重新添加到存活玩家列表
+ java.util.List alivePlayers = plugin.getGameStateManager().getAlivePlayers();
+ if (!alivePlayers.contains(targetId)) {
+ // 在新模式下,复活需要重新生成方块或将其重新加入游戏
+ sender.sendMessage(ChatColor.RED + "在新模式下,复活功能需要重新开始游戏或为玩家重新生成方块。");
+ sender.sendMessage(ChatColor.YELLOW + "建议使用 /pbl rstgm 重置游戏。");
+ } else {
+ target.setGameMode(org.bukkit.GameMode.SURVIVAL);
+ target.sendMessage(ChatColor.GREEN + "你已被复活!");
+ sender.sendMessage(ChatColor.GREEN + "玩家 " + target.getName() + " 已被复活!");
+ }
} else {
- sender.sendMessage("§c复活失败:生命系统未初始化");
+ target.setGameMode(org.bukkit.GameMode.SURVIVAL);
+ target.sendMessage(ChatColor.GREEN + "你已被复活!");
+ sender.sendMessage(ChatColor.GREEN + "玩家 " + target.getName() + " 已被复活!");
}
return true;
}
if (commandName.equals("pblstats")) {
- if (plugin.getBlockManager() == null) {
- sender.sendMessage("§c方块管理器未初始化");
- return true;
- }
-
- int totalPlayers = plugin.getBlockManager().getPlayerBlocksCount();
- int totalBlocks = plugin.getBlockManager().getTotalBlocksCount();
+ // 显示新模式下的游戏统计
+ GameStateManager gameStateManager = plugin.getGameStateManager();
+ GameStateManager.GameState currentState = gameStateManager.getCurrentState();
- // 获取统计标题
- String title = msgManager.getCommandMessage("pblstats", "title",
- "&6=== PlayerBlockLife 统计 ===");
- sender.sendMessage(title);
+ sender.sendMessage(ChatColor.GOLD + "=== PlayerBlockLife 游戏统计 ===");
+ sender.sendMessage(ChatColor.AQUA + "游戏状态: " + getStateText(currentState));
- // 在线玩家统计
- String onlineMsg = msgManager.getCommandMessage("pblstats", "online_players",
- "&e在线玩家: {count}");
- onlineMsg = onlineMsg.replace("{count}", String.valueOf(Bukkit.getOnlinePlayers().size()));
- sender.sendMessage(onlineMsg);
+ int onlineCount = Bukkit.getOnlinePlayers().size();
+ int opCount = 0;
+ int participantCount = 0;
- // 总方块统计
- String blocksMsg = msgManager.getCommandMessage("pblstats", "total_blocks",
- "&e总生命方块: {count}");
- blocksMsg = blocksMsg.replace("{count}", String.valueOf(totalBlocks));
- sender.sendMessage(blocksMsg);
-
- // 淘汰玩家统计
- int eliminatedCount = 0;
for (Player player : Bukkit.getOnlinePlayers()) {
- if (plugin.getBlockManager().getRemainingBlocks(player.getUniqueId()) == 0) {
- eliminatedCount++;
+ if (player.isOp()) {
+ opCount++;
+ } else {
+ participantCount++;
}
}
- String eliminatedMsg = msgManager.getCommandMessage("pblstats", "eliminated_players",
- "&e已淘汰玩家: {count}");
- eliminatedMsg = eliminatedMsg.replace("{count}", String.valueOf(eliminatedCount));
- sender.sendMessage(eliminatedMsg);
- sender.sendMessage("§7在线玩家详情:");
+ sender.sendMessage(ChatColor.YELLOW + "在线玩家: " + onlineCount +
+ " (参与者: " + participantCount + ", OP: " + opCount + ")");
+
+ if (currentState == GameStateManager.GameState.STARTED) {
+ int aliveCount = gameStateManager.getAlivePlayersCount();
+ sender.sendMessage(ChatColor.GREEN + "存活玩家: " + aliveCount);
+
+ if (gameStateManager.isLimitedTime()) {
+ long currentTime = System.currentTimeMillis();
+ long elapsedMinutes = (currentTime - gameStateManager.getGameStartTime()) / (1000 * 60);
+ long remainingMinutes = Math.max(0, gameStateManager.getGameDuration() - elapsedMinutes);
+ sender.sendMessage(ChatColor.YELLOW + "剩余时间: " + remainingMinutes + " 分钟");
+ }
+ }
+
+ sender.sendMessage(ChatColor.GRAY + "-------------------");
for (Player player : Bukkit.getOnlinePlayers()) {
- int blocks = plugin.getBlockManager().getRemainingBlocks(player.getUniqueId());
- String status = blocks > 0 ? "§a存活" : "§c已淘汰";
- sender.sendMessage("§7- " + player.getName() + ": §e" + blocks + " §7/ §a5 §7(" + status + "§7)");
+ int blocks = gameStateManager.getRemainingBlocks(player.getUniqueId());
+ String status = player.isOp() ? ChatColor.GOLD + "OP" :
+ (blocks > 0 ? ChatColor.GREEN + "存活" : ChatColor.RED + "淘汰");
+ sender.sendMessage(ChatColor.WHITE + "- " + player.getName() + ": " +
+ ChatColor.YELLOW + blocks + "/5 " +
+ ChatColor.GRAY + "(" + status + ChatColor.GRAY + ")");
}
- sender.sendMessage("§a=================================");
return true;
}
- String unknownMsg = msgManager.getMessage("game.errors.invalid_arguments",
- "&c未知的管理员命令!");
- sender.sendMessage(unknownMsg);
+ sender.sendMessage(ChatColor.RED + "未知的管理员命令!");
return true;
}
+
+ private String getStateText(GameStateManager.GameState state) {
+ switch (state) {
+ case WAITING: return ChatColor.YELLOW + "等待中";
+ case STARTED: return ChatColor.GREEN + "进行中";
+ case FINISHED: return ChatColor.RED + "已结束";
+ default: return ChatColor.GRAY + "未知";
+ }
+ }
}
\ No newline at end of file
diff --git a/src/main/java/com/playerblocklife/BlockBreakListener.java b/src/main/java/com/playerblocklife/BlockBreakListener.java
index 62e8a02..b020cbf 100644
--- a/src/main/java/com/playerblocklife/BlockBreakListener.java
+++ b/src/main/java/com/playerblocklife/BlockBreakListener.java
@@ -16,114 +16,159 @@ import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
+/**
+ * BlockBreakListener - PlayerBlockLife游戏模式下的方块破坏和放置监听器
+ *
+ * 监听玩家对生命方块的破坏行为,并在PlayerBlockLife游戏模式下进行相应处理:
+ *
+ * 检查被破坏的方块是否为生命方块(羊毛、玻璃、水泥)
+ * 验证破坏者是否有权限破坏该方块
+ * 更新玩家剩余生命方块数量
+ * 处理玩家淘汰逻辑
+ * 在游戏未开始时阻止方块破坏
+ *
+ *
+ * 此监听器与GameStateManager协作,确保游戏规则正确执行。
+ *
+ * @author xiaobai
+ * @version 2.2.0-1.20.4
+ * @since 1.0.0
+ */
public class BlockBreakListener implements Listener {
private final PlayerBlockLife plugin;
+ /**
+ * 构造一个新的方块破坏监听器
+ *
+ * @param plugin 插件主类实例
+ */
public BlockBreakListener(PlayerBlockLife plugin) {
this.plugin = plugin;
}
+ /**
+ * 处理方块被破坏的事件
+ *
+ * @param event 方块破坏事件
+ */
@EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true)
public void onBlockBreak(BlockBreakEvent event) {
Player breaker = event.getPlayer();
Block block = event.getBlock();
Location location = block.getLocation();
- if (block.getType() != Material.PLAYER_HEAD &&
- block.getType() != Material.PLAYER_WALL_HEAD) {
+ // 检查是否为生命方块(羊毛、玻璃或水泥块)
+ if (!isLifeBlockMaterial(block.getType())) {
return;
}
- UUID ownerId = plugin.getBlockManager().getBlockOwner(location);
- if (ownerId == null) {
+ // 检查游戏是否已开始
+ if (plugin.getGameStateManager().getCurrentState() != GameStateManager.GameState.STARTED) {
+ event.setCancelled(true);
+ breaker.sendMessage(org.bukkit.ChatColor.RED + "游戏尚未开始,无法破坏方块!");
return;
}
+ // 检查是否为玩家的生命方块
+ UUID ownerId = getBlockOwner(location);
+ if (ownerId == null) {
+ return; // 不是生命方块
+ }
+
if (ownerId.equals(breaker.getUniqueId())) {
if (breaker.getGameMode() != GameMode.CREATIVE) {
- String message = plugin.getMessageManager().getMessage("game.errors.cannot_break_own_block",
- "&c你不能挖掘自己的生命方块!");
- breaker.sendMessage(message);
+ breaker.sendMessage(org.bukkit.ChatColor.RED + "你不能挖掘自己的生命方块!");
event.setCancelled(true);
}
return;
}
- if (plugin.getBlockManager().removeBlock(location, breaker)) {
- int remaining = plugin.getBlockManager().getRemainingBlocks(ownerId);
+ if (plugin.getGameStateManager().removeBlock(location, breaker)) {
+ int remaining = plugin.getGameStateManager().getRemainingBlocks(ownerId);
Player owner = Bukkit.getPlayer(ownerId);
String ownerName = owner != null ? owner.getName() : Bukkit.getOfflinePlayer(ownerId).getName();
- if (remaining <= 0) {
- plugin.getLifeSystem().handlePlayerDeath(ownerId);
- }
-
// 通知破坏者
Map variables = new HashMap<>();
variables.put("owner", ownerName != null ? ownerName : "未知玩家");
variables.put("remaining", String.valueOf(remaining));
- variables.put("total", String.valueOf(plugin.getConfigManager().getBlocksPerPlayer()));
+ variables.put("total", String.valueOf(5)); // 使用固定值5,因为配置已移除
- String breakerMsg = plugin.getMessageManager().getFormattedMessage("game.block.destroyed.breaker",
- "&a你破坏了 {owner} 的生命方块!", variables);
- breaker.sendMessage(breakerMsg);
-
- String remainingMsg = plugin.getMessageManager().getFormattedMessage("game.block.destroyed.remaining",
- "&7剩余方块: {remaining}/{total}", variables);
- breaker.sendMessage(remainingMsg);
+ breaker.sendMessage(org.bukkit.ChatColor.GREEN + "你破坏了 " + org.bukkit.ChatColor.YELLOW +
+ (ownerName != null ? ownerName : "未知玩家") + org.bukkit.ChatColor.GREEN + " 的生命方块!");
+ breaker.sendMessage(org.bukkit.ChatColor.GRAY + "剩余方块: " + org.bukkit.ChatColor.YELLOW +
+ remaining + org.bukkit.ChatColor.GRAY + "/" + org.bukkit.ChatColor.RED + 5);
if (remaining == 1) {
- String lastBlockMsg = plugin.getMessageManager().getMessage("game.block.last_block_warning",
- "&6⚡ 对方只剩最后1个生命方块了!");
- breaker.sendMessage(lastBlockMsg);
+ breaker.sendMessage(org.bukkit.ChatColor.GOLD + "⚡ 对方只剩最后1个生命方块了!");
breaker.playSound(breaker.getLocation(),
org.bukkit.Sound.ENTITY_PLAYER_LEVELUP, 1.0f, 1.5f);
}
// 通知方块所有者
if (owner != null && owner.isOnline()) {
- variables.put("breaker", breaker.getName());
- String ownerMsg = plugin.getMessageManager().getFormattedMessage("game.block.destroyed.owner",
- "&c⚠ 警告!你的生命方块被 {breaker} 破坏了!剩余: {remaining}/{total}", variables);
- owner.sendMessage(ownerMsg);
+ owner.sendMessage(org.bukkit.ChatColor.RED + "⚠ 警告!你的生命方块被 " +
+ org.bukkit.ChatColor.YELLOW + breaker.getName() + org.bukkit.ChatColor.RED + " 破坏了!");
+ owner.sendMessage(org.bukkit.ChatColor.GRAY + "剩余方块: " + org.bukkit.ChatColor.YELLOW +
+ remaining + org.bukkit.ChatColor.GRAY + "/" + org.bukkit.ChatColor.RED + 5);
if (remaining == 0) {
- String allDestroyedMsg = plugin.getMessageManager().getMessage("game.block.all_destroyed",
- "&c☠ 你的所有生命方块已被破坏!你已被淘汰!");
- owner.sendMessage(allDestroyedMsg);
+ owner.sendMessage(org.bukkit.ChatColor.DARK_RED + "☠ 你的所有生命方块已被破坏!你已被淘汰!");
}
}
// 广播(如果启用)
if (plugin.getConfigManager().isBroadcastOnBlockBreak()) {
- String broadcastMsg = plugin.getMessageManager().getFormattedMessage("broadcast.block_destroyed",
- "&6{breaker} &7破坏了 &c{owner} &7的生命方块!", variables);
int range = plugin.getConfigManager().getBroadcastRange();
for (Player nearby : breaker.getWorld().getPlayers()) {
if (nearby.getLocation().distance(breaker.getLocation()) <= range &&
nearby != breaker && (owner == null || nearby != owner)) {
- nearby.sendMessage(broadcastMsg);
+ nearby.sendMessage(org.bukkit.ChatColor.GOLD + breaker.getName() +
+ org.bukkit.ChatColor.GRAY + " 破坏了 " + org.bukkit.ChatColor.YELLOW +
+ (ownerName != null ? ownerName : "某人") + org.bukkit.ChatColor.GRAY + " 的生命方块!");
}
}
}
}
}
+ /**
+ * 处理方块被放置的事件
+ *
+ * @param event 方块放置事件
+ */
@EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true)
public void onBlockPlace(BlockPlaceEvent event) {
Player player = event.getPlayer();
Block block = event.getBlock();
- if (block.getType() == Material.PLAYER_HEAD ||
- block.getType() == Material.PLAYER_WALL_HEAD) {
-
- UUID ownerId = plugin.getBlockManager().getBlockOwner(block.getLocation());
- if (ownerId != null && !ownerId.equals(player.getUniqueId())) {
- String message = plugin.getMessageManager().getMessage("game.errors.cannot_place_in_block_area",
- "&c你不能在这里放置方块,这是别人的生命方块区域!");
- player.sendMessage(message);
- event.setCancelled(true);
- }
+ // 检查是否为生命方块材料
+ if (isLifeBlockMaterial(block.getType())) {
+ player.sendMessage(org.bukkit.ChatColor.RED + "你不能放置生命方块!");
+ event.setCancelled(true);
+ return;
}
}
+
+ /**
+ * 检查材料是否为生命方块材料
+ */
+ private boolean isLifeBlockMaterial(Material material) {
+ return material.name().endsWith("_WOOL") ||
+ material.name().endsWith("_STAINED_GLASS") ||
+ material.name().endsWith("_CONCRETE");
+ }
+
+ /**
+ * 获取方块所有者
+ */
+ private UUID getBlockOwner(Location location) {
+ // 检查是否有任何玩家的方块在此位置
+ for (Map.Entry> entry : plugin.getGameStateManager().getPlayerBlocks().entrySet()) {
+ if (entry.getValue().contains(location)) {
+ return entry.getKey();
+ }
+ }
+ return null;
+ }
}
\ No newline at end of file
diff --git a/src/main/java/com/playerblocklife/CheckLifeBlocksCommand.java b/src/main/java/com/playerblocklife/CheckLifeBlocksCommand.java
index b84712e..207b151 100644
--- a/src/main/java/com/playerblocklife/CheckLifeBlocksCommand.java
+++ b/src/main/java/com/playerblocklife/CheckLifeBlocksCommand.java
@@ -1,101 +1,94 @@
package com.playerblocklife;
-import org.bukkit.Bukkit;
-import org.bukkit.Location;
+import org.bukkit.ChatColor;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
-import java.util.List;
-import java.util.UUID;
-
+/**
+ * CheckLifeBlocksCommand - 显示玩家在PlayerBlockLife游戏模式下的生命方块状态
+ *
+ * 在PlayerBlockLife游戏模式下,此命令显示玩家当前的剩余生命方块数、游戏状态和分配的颜色。
+ * 与旧模式不同,此命令不再显示生命方块的具体位置,而是提供当前游戏状态信息。
+ *
+ * @author xiaobai
+ * @version 2.2.0-1.20.4
+ * @since 1.0.0
+ */
public class CheckLifeBlocksCommand implements CommandExecutor {
private final PlayerBlockLife plugin;
+ /**
+ * 构造一个新的检查生命方块命令执行器
+ *
+ * @param plugin 插件主类实例
+ */
public CheckLifeBlocksCommand(PlayerBlockLife plugin) {
this.plugin = plugin;
}
@Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
- // 检查命令是否启用
- if (!plugin.getConfigManager().isCommandEnabled("checklifeblocks")) {
- String message = plugin.getMessageManager().getMessage("game.errors.command_disabled",
- "&c此命令已被禁用!");
- sender.sendMessage(message);
- return true;
- }
-
if (!(sender instanceof Player)) {
- String message = plugin.getMessageManager().getMessage("game.errors.no_permission",
- "&c只有玩家可以使用此命令!");
- sender.sendMessage(message);
+ sender.sendMessage(ChatColor.RED + "此命令只能由玩家执行!控制台无法查看具体玩家的生命方块状态。");
return true;
}
Player player = (Player) sender;
- UUID playerId = player.getUniqueId();
+
+ // 获取当前玩家在新模式下的剩余方块数
+ int remainingBlocks = plugin.getGameStateManager().getRemainingBlocks(player.getUniqueId());
- // 检查是否允许玩家自己使用
- if (!plugin.getConfigManager().isSelfUseAllowed("checklifeblocks")) {
- String message = plugin.getMessageManager().getMessage("game.errors.no_permission",
- "&c你没有权限使用此命令!");
- player.sendMessage(message);
- return true;
- }
-
- List blocks = plugin.getBlockManager().getPlayerBlocks(playerId);
- int remaining = blocks.size();
- MessageManager msgManager = plugin.getMessageManager();
-
- if (remaining == 0) {
- String noBlocksMsg = msgManager.getCommandMessage("checklifeblocks", "no_blocks",
- "&c你还没有生命方块!");
- player.sendMessage(noBlocksMsg);
- return true;
- }
-
- // 获取消息
- String successMsg = msgManager.getCommandMessage("checklifeblocks", "success",
- "&e你的生命方块位置:");
- player.sendMessage(successMsg);
+ player.sendMessage(ChatColor.AQUA + "=== 你的生命方块状态 ===");
+ player.sendMessage(ChatColor.GRAY + "剩余方块数量: " + ChatColor.YELLOW + remainingBlocks +
+ ChatColor.GRAY + " / " + ChatColor.RED + 5);
- player.sendMessage("§7剩余方块数量: §e" + remaining + " §7/ §a5");
+ // 根据游戏状态显示不同信息
+ GameStateManager.GameState state = plugin.getGameStateManager().getCurrentState();
+ switch (state) {
+ case WAITING:
+ player.sendMessage(ChatColor.YELLOW + "游戏状态: 等待开始");
+ break;
+ case STARTED:
+ player.sendMessage(ChatColor.GREEN + "游戏状态: 进行中");
+ break;
+ case FINISHED:
+ player.sendMessage(ChatColor.RED + "游戏状态: 已结束");
+ break;
+ }
- // 显示生命值(如果启用)
- if (plugin.getConfigManager().isHealthSystemEnabled()) {
- Integer health = plugin.getLifeSystem().getPlayerHealth(playerId);
- player.sendMessage("§7当前生命值: §c" + (health != null ? health : "20") + " ❤");
+ // 显示玩家颜色
+ org.bukkit.Material playerColor = plugin.getGameStateManager().getPlayerColor(player.getUniqueId());
+ if (playerColor != null) {
+ String colorName = getColorName(playerColor);
+ player.sendMessage(ChatColor.GRAY + "你的方块颜色: " + ChatColor.AQUA + colorName);
}
-
- if (remaining <= 2) {
- String warningMsg = msgManager.getMessage("game.block.warning_low_blocks",
- "&c⚠ 警告!生命方块即将耗尽!");
- player.sendMessage(warningMsg);
- }
-
- player.sendMessage("§7方块位置:");
- for (int i = 0; i < blocks.size(); i++) {
- Location loc = blocks.get(i);
- String worldName = loc.getWorld() != null ? loc.getWorld().getName() : "未知世界";
- String locationMsg = msgManager.getMessage("game.block.location_item",
- "&7- {world} ({x}, {y}, {z})");
- locationMsg = locationMsg.replace("{world}", worldName)
- .replace("{x}", String.valueOf(loc.getBlockX()))
- .replace("{y}", String.valueOf(loc.getBlockY()))
- .replace("{z}", String.valueOf(loc.getBlockZ()));
- player.sendMessage(locationMsg);
- }
-
- if (remaining > 0) {
- Location nearestBlock = plugin.getBlockManager().getNearestBlock(player);
- if (nearestBlock != null) {
- double distance = player.getLocation().distance(nearestBlock);
- player.sendMessage("§7最近方块距离: §a" + (int)distance + " §7格");
- }
- }
-
+
return true;
}
+
+ /**
+ * 获取材料颜色名称
+ */
+ private String getColorName(org.bukkit.Material material) {
+ String name = material.name();
+ if (name.contains("WHITE")) return "白色";
+ if (name.contains("ORANGE")) return "橙色";
+ if (name.contains("MAGENTA")) return "品红色";
+ if (name.contains("LIGHT_BLUE")) return "淡蓝色";
+ if (name.contains("YELLOW")) return "黄色";
+ if (name.contains("LIME")) return "黄绿色";
+ if (name.contains("PINK")) return "粉色";
+ if (name.contains("GRAY")) return "灰色";
+ if (name.contains("LIGHT_GRAY")) return "淡灰色";
+ if (name.contains("CYAN")) return "青色";
+ if (name.contains("PURPLE")) return "紫色";
+ if (name.contains("BLUE")) return "蓝色";
+ if (name.contains("BROWN")) return "棕色";
+ if (name.contains("GREEN")) return "绿色";
+ if (name.contains("RED")) return "红色";
+ if (name.contains("BLACK")) return "黑色";
+ return "未知";
+ }
}
\ No newline at end of file
diff --git a/src/main/java/com/playerblocklife/ConfigManager.java b/src/main/java/com/playerblocklife/ConfigManager.java
index 4c338a6..5a2ee00 100644
--- a/src/main/java/com/playerblocklife/ConfigManager.java
+++ b/src/main/java/com/playerblocklife/ConfigManager.java
@@ -22,17 +22,16 @@ import java.nio.charset.StandardCharsets;
* 支持多种皮肤来源的优先级配置
*
*
- * SkinsRestorer配置支持:
+ *
SkinsRestorer配置支持:
*
* skin.source:皮肤来源优先级(skinsrestorer/player_profile/local_cache)
* skin.use-skinsrestorer:是否启用SkinsRestorer支持
* skin.cache.expire_days:皮肤缓存过期时间
* 默认配置已优化,优先使用SkinsRestorer以支持离线服务器
*
- *
*
* @author xiaobai
- * @version 2.1.0
+ * @version 2.2.0-1.20.4
* @since 1.0.0
*/
public class ConfigManager {
@@ -126,14 +125,6 @@ public class ConfigManager {
*/
private void updateConfig(int fromVersion, int toVersion) {
if (fromVersion == 1 && toVersion == 2) {
- // 添加自动生成配置
- if (!config.contains("auto-generation.enabled")) {
- config.set("auto-generation.enabled", true);
- config.set("auto-generation.require_open_sky", true);
- config.set("auto-generation.max_attempts", 50);
- config.set("auto-generation.on_failure", "notify");
- }
-
// 添加命令启用配置
if (!config.contains("commands.setlifeblocks.enabled")) {
config.set("commands.setlifeblocks.enabled", true);
@@ -206,195 +197,201 @@ public class ConfigManager {
return config;
}
- // 以下为配置项的获取方法
-
- public int getBlocksPerPlayer() {
- return getConfig().getInt("blocks.amount", 5);
- }
-
- public int getSpreadRange() {
- return getConfig().getInt("blocks.spread", 5);
- }
-
- public int getMinDistance() {
- return getConfig().getInt("blocks.min-distance", 10);
- }
-
- public int getDepth() {
- return getConfig().getInt("blocks.depth", -1);
- }
-
- public String getBlockMaterial() {
- return getConfig().getString("blocks.material", "player_head");
- }
-
+ /**
+ * 检查玩家方块被挖光时是否死亡
+ *
+ * @return 如果玩家方块被挖光时死亡则返回true,否则返回false
+ */
public boolean isDieWhenBlocksGone() {
return getConfig().getBoolean("game.die_when_blocks_gone", true);
}
+ /**
+ * 检查玩家死亡后是否成为观察者
+ *
+ * @return 如果玩家死亡后成为观察者则返回true,否则返回false
+ */
public boolean isBecomeSpectator() {
return getConfig().getBoolean("game.become_spectator", true);
}
+ /**
+ * 检查是否启用生命值系统
+ *
+ * @return 如果启用生命值系统则返回true,否则返回false
+ */
public boolean isHealthSystemEnabled() {
return getConfig().getBoolean("game.health_system", true);
}
- public boolean isSkinSystemEnabled() {
- return getConfig().getBoolean("skin.enabled", true);
- }
-
/**
- * 获取皮肤来源配置
+ * 检查是否启用自动保存
*
- * 支持的皮肤来源:
- *
- * skinsrestorer :优先从SkinsRestorer插件获取皮肤纹理数据
- *
- * 推荐用于离线服务器
- * 支持玩家自定义皮肤
- * 避免默认Steve皮肤问题
- *
- *
- * player_profile :优先使用Bukkit的PlayerProfile API
- *
- * 需要玩家在线验证
- * 适合在线服务器
- * 支持Mojang官方皮肤
- *
- *
- * local_cache :优先从本地缓存加载皮肤数据
- *
- * 减少网络请求
- * 提高加载速度
- * 支持离线使用
- *
- *
- *
- *
- *
- * 默认配置已将此值设为"skinsrestorer",以优化离线服务器体验。
- *
- * @return 皮肤来源配置值
- * @see #useSkinsRestorer()
- * @see SkinManager#loadPlayerSkinAsync()
+ * @return 如果启用自动保存则返回true,否则返回false
*/
- public String getSkinSource() {
- return getConfig().getString("skin.source", "skinsrestorer");
- }
-
- /**
- * 检查是否启用SkinsRestorer插件支持
- *
- * 当此方法返回true时,插件将:
- *
- * 优先从SkinsRestorer插件获取玩家皮肤纹理
- * 支持离线服务器获取玩家自定义皮肤
- * 避免方块总是显示默认Steve皮肤的问题
- * 使用反射安全调用SkinsRestorer API,无需硬依赖
- *
- *
- *
- * 默认配置已将此值设为true,以优化离线服务器体验。
- *
- * @return 如果启用SkinsRestorer支持返回true,否则返回false
- * @see #getSkinSource()
- */
- public boolean useSkinsRestorer() {
- return getConfig().getBoolean("skin.use-skinsrestorer", true);
- }
-
- public int getCacheExpireDays() {
- return getConfig().getInt("skin.cache.expire_days", 7);
- }
-
public boolean isAutoSaveEnabled() {
return getConfig().getBoolean("storage.auto_save.enabled", true);
}
+ /**
+ * 获取自动保存间隔
+ *
+ * @return 自动保存间隔(秒),默认为300秒(5分钟)
+ */
public int getAutoSaveInterval() {
return getConfig().getInt("storage.auto_save.interval", 300);
}
+ /**
+ * 获取存储类型配置
+ *
+ * @return 存储类型配置值,默认为"yaml"
+ */
public String getStorageType() {
return getConfig().getString("storage.type", "yaml");
}
+ /**
+ * 检查是否在方块被破坏时广播消息
+ *
+ * @return 如果在方块被破坏时广播消息则返回true,否则返回false
+ */
public boolean isBroadcastOnBlockBreak() {
return getConfig().getBoolean("game.broadcast.on_block_break", true);
}
+ /**
+ * 检查是否在玩家死亡时广播消息
+ *
+ * @return 如果在玩家死亡时广播消息则返回true,否则返回false
+ */
public boolean isBroadcastOnPlayerDeath() {
return getConfig().getBoolean("game.broadcast.on_player_death", true);
}
+ /**
+ * 获取广播范围
+ *
+ * @return 广播范围(方块),默认为30
+ */
public int getBroadcastRange() {
return getConfig().getInt("game.broadcast.range", 30);
}
+ /**
+ * 检查是否给予挖掘奖励经验
+ *
+ * @return 如果给予挖掘奖励经验则返回true,否则返回false
+ */
public boolean isGiveExpReward() {
return getConfig().getBoolean("game.break_rewards.give_exp", true);
}
+ /**
+ * 获取奖励经验数量
+ *
+ * @return 奖励经验数量,默认为5
+ */
public int getExpRewardAmount() {
return getConfig().getInt("game.break_rewards.exp_amount", 5);
}
+ /**
+ * 检查是否保护方块免受爆炸破坏
+ *
+ * @return 如果保护方块免受爆炸破坏则返回true,否则返回false
+ */
public boolean isProtectFromExplosions() {
return getConfig().getBoolean("protection.protect_from_explosions", true);
}
+ /**
+ * 检查是否保护方块免受火灾破坏
+ *
+ * @return 如果保护方块免受火灾破坏则返回true,否则返回false
+ */
public boolean isProtectFromFire() {
return getConfig().getBoolean("protection.protect_from_fire", true);
}
+ /**
+ * 检查是否保护方块免受活塞推动
+ *
+ * @return 如果保护方块免受活塞推动则返回true,否则返回false
+ */
public boolean isProtectFromPistons() {
return getConfig().getBoolean("protection.protect_from_pistons", true);
}
- // 自动生成配置获取方法
- public boolean isAutoGenerationEnabled() {
- return getConfig().getBoolean("auto-generation.enabled", true);
- }
- public boolean isRequireOpenSky() {
- return getConfig().getBoolean("auto-generation.require_open_sky", true);
- }
-
- public int getMaxAttempts() {
- return getConfig().getInt("auto-generation.max_attempts", 50);
- }
-
- public String getOnFailureAction() {
- return getConfig().getString("auto-generation.on_failure", "notify");
- }
// 命令启用配置获取方法
+ /**
+ * 检查命令是否启用
+ *
+ * @param commandName 命令名称
+ * @return 如果命令启用则返回true,否则返回false
+ */
public boolean isCommandEnabled(String commandName) {
return getConfig().getBoolean("commands." + commandName + ".enabled", true);
}
+ /**
+ * 检查是否允许玩家自己使用命令
+ *
+ * @param commandName 命令名称
+ * @return 如果允许玩家自己使用命令则返回true,否则返回false
+ */
public boolean isSelfUseAllowed(String commandName) {
return getConfig().getBoolean("commands." + commandName + ".allow_self_use", true);
}
+ /**
+ * 检查是否允许管理员使用命令
+ *
+ * @param commandName 命令名称
+ * @return 如果允许管理员使用命令则返回true,否则返回false
+ */
public boolean isAdminUseAllowed(String commandName) {
return getConfig().getBoolean("commands." + commandName + ".allow_admin_use", true);
}
+ /**
+ * 检查命令是否仅管理员可用
+ *
+ * @param commandName 命令名称
+ * @return 如果命令仅管理员可用则返回true,否则返回false
+ */
public boolean isAdminOnly(String commandName) {
return getConfig().getBoolean("commands." + commandName + ".admin_only", false);
}
// 消息文件配置获取方法
+ /**
+ * 检查是否使用外部消息文件
+ *
+ * @return 如果使用外部消息文件则返回true,否则返回false
+ */
public boolean useExternalMessageFile() {
return getConfig().getBoolean("messages.use_external_file", true);
}
+ /**
+ * 获取外部消息文件名
+ *
+ * @return 外部消息文件名,默认为"messages.yml"
+ */
public String getExternalMessageFileName() {
return getConfig().getString("messages.external_file", "messages.yml");
}
+ /**
+ * 获取消息
+ *
+ * @param path 消息路径
+ * @param defaultValue 默认值
+ * @return 消息内容
+ */
public String getMessage(String path, String defaultValue) {
// 优先从外部消息文件获取
if (useExternalMessageFile()) {
diff --git a/src/main/java/com/playerblocklife/GameStateManager.java b/src/main/java/com/playerblocklife/GameStateManager.java
new file mode 100644
index 0000000..1176122
--- /dev/null
+++ b/src/main/java/com/playerblocklife/GameStateManager.java
@@ -0,0 +1,883 @@
+package com.playerblocklife;
+
+import org.bukkit.Bukkit;
+import org.bukkit.ChatColor;
+import org.bukkit.GameMode;
+import org.bukkit.Location;
+import org.bukkit.Material;
+import org.bukkit.World;
+import org.bukkit.block.Block;
+import org.bukkit.entity.Player;
+import org.bukkit.scoreboard.DisplaySlot;
+import org.bukkit.scoreboard.Objective;
+import org.bukkit.scoreboard.Scoreboard;
+import org.bukkit.scoreboard.ScoreboardManager;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Random;
+import java.util.UUID;
+
+/**
+ * 游戏状态管理器 - PlayerBlockLife生存游戏模式的核心管理器
+ *
+ * 主要功能包括:
+ *
+ * 管理游戏状态(等待中/游戏中/已结束)
+ * 为非OP玩家分配独特颜色的生命方块
+ * 处理游戏开始时的生命方块生成
+ * 控制游戏开始和结束逻辑
+ * 管理实时计分板显示(游戏状态、剩余方块、限时等)
+ * 处理游戏重置和玩家淘汰
+ * 监控游戏结束条件(仅剩一名存活玩家或限时结束)
+ *
+ *
+ * 游戏模式特性:
+ *
+ * OP玩家始终处于观察者模式,不参与游戏
+ * 非OP玩家在等待时处于冒险模式,手中持有对应颜色方块
+ * 游戏开始后非OP玩家转为生存模式并清空背包
+ * 使用不同颜色的羊毛、玻璃、水泥方块作为生命方块
+ * 支持限时游戏模式
+ * 游戏结束时显示胜利玩家
+ *
+ *
+ * @author xiaobai
+ * @version 2.2.0-1.20.4
+ * @since 4.0.0
+ */
+public class GameStateManager {
+ public enum GameState {
+ WAITING, // 等待开始
+ STARTED, // 游戏进行中
+ FINISHED // 游戏结束
+ }
+
+ private final PlayerBlockLife plugin;
+ private GameState currentState = GameState.WAITING;
+ private final Map playerColors = new HashMap<>(); // 玩家颜色分配
+ private final Map> playerBlocks = new HashMap<>(); // 玩家方块位置
+ private final List alivePlayers = new ArrayList<>(); // 存活玩家
+ private int gameDuration = 0; // 限时游戏持续时间(分钟)
+ private long gameStartTime = 0; // 游戏开始时间戳
+ private boolean isLimitedTime = false; // 是否为限时游戏
+ private final ScoreboardManager scoreboardManager;
+ private Scoreboard gameScoreboard;
+ private Objective gameObjective;
+
+ // 可用的羊毛、玻璃、水泥颜色
+ private static final Material[] WOOL_COLORS = {
+ Material.WHITE_WOOL, Material.ORANGE_WOOL, Material.MAGENTA_WOOL, Material.LIGHT_BLUE_WOOL,
+ Material.YELLOW_WOOL, Material.LIME_WOOL, Material.PINK_WOOL, Material.GRAY_WOOL,
+ Material.LIGHT_GRAY_WOOL, Material.CYAN_WOOL, Material.PURPLE_WOOL, Material.BLUE_WOOL,
+ Material.BROWN_WOOL, Material.GREEN_WOOL, Material.RED_WOOL, Material.BLACK_WOOL
+ };
+
+ private static final Material[] GLASS_COLORS = {
+ Material.WHITE_STAINED_GLASS, Material.ORANGE_STAINED_GLASS, Material.MAGENTA_STAINED_GLASS, Material.LIGHT_BLUE_STAINED_GLASS,
+ Material.YELLOW_STAINED_GLASS, Material.LIME_STAINED_GLASS, Material.PINK_STAINED_GLASS, Material.GRAY_STAINED_GLASS,
+ Material.LIGHT_GRAY_STAINED_GLASS, Material.CYAN_STAINED_GLASS, Material.PURPLE_STAINED_GLASS, Material.BLUE_STAINED_GLASS,
+ Material.BROWN_STAINED_GLASS, Material.GREEN_STAINED_GLASS, Material.RED_STAINED_GLASS, Material.BLACK_STAINED_GLASS
+ };
+
+ private static final Material[] CONCRETE_COLORS = {
+ Material.WHITE_CONCRETE, Material.ORANGE_CONCRETE, Material.MAGENTA_CONCRETE, Material.LIGHT_BLUE_CONCRETE,
+ Material.YELLOW_CONCRETE, Material.LIME_CONCRETE, Material.PINK_CONCRETE, Material.GRAY_CONCRETE,
+ Material.LIGHT_GRAY_CONCRETE, Material.CYAN_CONCRETE, Material.PURPLE_CONCRETE, Material.BLUE_CONCRETE,
+ Material.BROWN_CONCRETE, Material.GREEN_CONCRETE, Material.RED_CONCRETE, Material.BLACK_CONCRETE
+ };
+
+ public GameStateManager(PlayerBlockLife plugin) {
+ this.plugin = plugin;
+ this.scoreboardManager = Bukkit.getScoreboardManager();
+ createGameScoreboard();
+ }
+
+ private void createGameScoreboard() {
+ this.gameScoreboard = scoreboardManager.getNewScoreboard();
+ this.gameObjective = gameScoreboard.registerNewObjective("pbl_game", "dummy", ChatColor.GOLD + "PlayerBlockLife 游戏");
+ this.gameObjective.setDisplaySlot(DisplaySlot.SIDEBAR);
+
+ // 初始化计分板内容
+ updateScoreboard();
+ }
+
+ public GameState getCurrentState() {
+ return currentState;
+ }
+
+ public void setCurrentState(GameState state) {
+ this.currentState = state;
+ updateScoreboard();
+
+ // 根据新状态更新玩家
+ switch (state) {
+ case WAITING:
+ for (Player player : Bukkit.getOnlinePlayers()) {
+ if (player.isOp()) {
+ player.setGameMode(GameMode.SPECTATOR);
+ } else {
+ player.setGameMode(GameMode.ADVENTURE);
+ player.getInventory().clear();
+ giveColorBlockToPlayer(player);
+ }
+ }
+ break;
+ case STARTED:
+ // 游戏开始后,非OP玩家设置为生存模式并清空背包
+ for (Player player : Bukkit.getOnlinePlayers()) {
+ if (!player.isOp()) {
+ player.setGameMode(GameMode.SURVIVAL);
+ player.getInventory().clear();
+ // 添加玩家到存活列表(如果他们有分配的颜色,意味着他们是参与者)
+ if (playerColors.containsKey(player.getUniqueId())) {
+ if (!alivePlayers.contains(player.getUniqueId())) {
+ alivePlayers.add(player.getUniqueId());
+ }
+ }
+ }
+ }
+ gameStartTime = System.currentTimeMillis();
+ break;
+ case FINISHED:
+ // 游戏结束后,所有玩家设置为冒险模式
+ for (Player player : Bukkit.getOnlinePlayers()) {
+ if (player.isOp()) {
+ player.setGameMode(GameMode.SPECTATOR);
+ } else {
+ player.setGameMode(GameMode.ADVENTURE);
+ }
+ }
+ break;
+ }
+ }
+
+ /**
+ * 分配玩家颜色
+ */
+ /**
+ * 为玩家分配颜色
+ *
+ * @param player 要分配颜色的玩家
+ */
+ public void assignPlayerColor(Player player) {
+ if (playerColors.containsKey(player.getUniqueId())) {
+ return; // 已分配颜色
+ }
+
+ // 如果所有颜色都已分配,将玩家设置为观察者模式
+ if (playerColors.size() >= WOOL_COLORS.length) {
+ player.setGameMode(GameMode.SPECTATOR);
+ player.sendMessage(ChatColor.YELLOW + "所有颜色都已分配,您将作为观察者参与游戏。");
+ return;
+ }
+
+ // 随机分配一个未使用的颜色
+ List availableColors = new ArrayList<>();
+ for (Material wool : WOOL_COLORS) {
+ boolean isUsed = false;
+ for (Material usedColor : playerColors.values()) {
+ if (usedColor == wool) {
+ isUsed = true;
+ break;
+ }
+ }
+ if (!isUsed) {
+ availableColors.add(wool);
+ }
+ }
+
+ if (!availableColors.isEmpty()) {
+ Material selectedColor = availableColors.get(new Random().nextInt(availableColors.size()));
+ playerColors.put(player.getUniqueId(), selectedColor);
+
+ // 给玩家对应颜色的方块
+ giveColorBlockToPlayer(player);
+
+ String colorName = getMaterialColorName(selectedColor);
+ player.sendMessage(ChatColor.GREEN + "您被分配了 " + ChatColor.AQUA + colorName + ChatColor.GREEN + " 颜色的生命方块!");
+ }
+ }
+
+ /**
+ * 给玩家对应颜色的方块
+ */
+ private void giveColorBlockToPlayer(Player player) {
+ Material color = playerColors.get(player.getUniqueId());
+ if (color != null) {
+ player.getInventory().clear();
+ player.getInventory().addItem(new org.bukkit.inventory.ItemStack(color, 1));
+ }
+ }
+
+ /**
+ * 获取材料的颜色名称
+ */
+ private String getMaterialColorName(Material material) {
+ String name = material.name();
+ if (name.contains("WHITE")) return "白色";
+ if (name.contains("ORANGE")) return "橙色";
+ if (name.contains("MAGENTA")) return "品红色";
+ if (name.contains("LIGHT_BLUE")) return "淡蓝色";
+ if (name.contains("YELLOW")) return "黄色";
+ if (name.contains("LIME")) return "黄绿色";
+ if (name.contains("PINK")) return "粉色";
+ if (name.contains("GRAY")) return "灰色";
+ if (name.contains("LIGHT_GRAY")) return "淡灰色";
+ if (name.contains("CYAN")) return "青色";
+ if (name.contains("PURPLE")) return "紫色";
+ if (name.contains("BLUE")) return "蓝色";
+ if (name.contains("BROWN")) return "棕色";
+ if (name.contains("GREEN")) return "绿色";
+ if (name.contains("RED")) return "红色";
+ if (name.contains("BLACK")) return "黑色";
+ return "未知";
+ }
+
+ /**
+ * 为玩家生成生命方块
+ */
+ /**
+ * 为玩家生成生命方块
+ *
+ * @param player 要生成生命方块的玩家
+ * @return 生成成功返回true,否则返回false
+ */
+ public boolean generateLifeBlocksForPlayer(Player player) {
+ if (!playerColors.containsKey(player.getUniqueId()) || currentState != GameState.STARTED) {
+ return false;
+ }
+
+ Material color = playerColors.get(player.getUniqueId());
+ ConfigManager config = plugin.getConfigManager();
+ int blockAmount = 5; // 使用固定值,因为配置已移除
+ int spreadRange = 5; // 使用固定值,因为配置已移除
+
+ List blocks = new ArrayList<>();
+ int blocksPlaced = 0;
+ int attempts = 0;
+ Random random = new Random();
+
+ // 尝试生成指定数量的方块
+ while (blocksPlaced < blockAmount && attempts < 50) {
+ Location center = player.getLocation();
+ int x = random.nextInt(spreadRange * 2 + 1) - spreadRange;
+ int z = random.nextInt(spreadRange * 2 + 1) - spreadRange;
+
+ Location testLoc = center.clone().add(x, 0, z);
+ World world = testLoc.getWorld();
+ if (world == null) continue;
+
+ // 从世界最高点向下寻找第一个固体方块
+ int maxHeight = world.getMaxHeight();
+ Block groundBlock = null;
+ for (int y = maxHeight; y > world.getMinHeight(); y--) {
+ testLoc.setY(y);
+ Block block = testLoc.getBlock();
+ Material type = block.getType();
+
+ // 检查是否是固体方块(可以作为支撑)
+ if (type.isSolid() && type.isBlock() && !type.isTransparent()) {
+ groundBlock = block;
+ break;
+ }
+ }
+
+ if (groundBlock == null) {
+ attempts++;
+ continue;
+ }
+
+ // 地表位置 = 固体方块上方一格
+ Location surfaceLoc = groundBlock.getLocation().add(0, 1, 0);
+
+ // 检查是否已有方块
+ if (isLifeBlock(surfaceLoc)) {
+ attempts++;
+ continue;
+ }
+
+ // 检查位置是否合适
+ if (!isSuitableLocation(surfaceLoc)) {
+ attempts++;
+ continue;
+ }
+
+ // 放置方块
+ surfaceLoc.getBlock().setType(color);
+ blocks.add(surfaceLoc);
+ blocksPlaced++;
+
+ // 添加放置效果
+ spawnPlaceEffects(surfaceLoc);
+ }
+
+ if (blocksPlaced > 0) {
+ playerBlocks.put(player.getUniqueId(), blocks);
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * 检查位置是否适合放置生命方块
+ */
+ private boolean isSuitableLocation(Location location) {
+ Block block = location.getBlock();
+
+ // 检查是否已有方块
+ if (isLifeBlock(location)) {
+ return false;
+ }
+
+ // 检查方块是否可替换
+ Material type = block.getType();
+ if (!type.isAir()) {
+ // 固体方块不能替换
+ if (type.isSolid()) {
+ return false;
+ }
+
+ // 液体方块不能替换
+ if (type == Material.WATER || type == Material.LAVA) {
+ return false;
+ }
+ }
+
+ // 检查下方是否有支撑方块
+ Block below = location.clone().add(0, -1, 0).getBlock();
+ Material belowType = below.getType();
+
+ if (belowType.isAir() || belowType == Material.WATER || belowType == Material.LAVA) {
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * 生成放置效果
+ */
+ private void spawnPlaceEffects(Location location) {
+ if (location.getWorld() != null) {
+ // 粒子效果
+ location.getWorld().spawnParticle(org.bukkit.Particle.ENCHANTMENT_TABLE,
+ location.clone().add(0.5, 0.5, 0.5),
+ 30, 0.3, 0.3, 0.3, 0.1);
+
+ // 音效
+ location.getWorld().playSound(location, org.bukkit.Sound.BLOCK_ANVIL_PLACE, 0.5f, 1.2f);
+ }
+ }
+
+ /**
+ * 检查是否为生命方块
+ */
+ private boolean isLifeBlock(Location location) {
+ // 检查是否有任何玩家的方块在此位置
+ for (List playerBlockList : playerBlocks.values()) {
+ if (playerBlockList.contains(location)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * 移除被破坏的生命方块
+ */
+ /**
+ * 移除被破坏的生命方块
+ *
+ * @param location 方块位置
+ * @param breaker 破坏方块的玩家
+ * @return 移除成功返回true,否则返回false
+ */
+ public boolean removeBlock(Location location, Player breaker) {
+ // 找到此位置属于哪个玩家的方块
+ UUID ownerId = null;
+ for (Map.Entry> entry : playerBlocks.entrySet()) {
+ if (entry.getValue().contains(location)) {
+ ownerId = entry.getKey();
+ break;
+ }
+ }
+
+ if (ownerId == null) {
+ return false;
+ }
+
+ List blocks = playerBlocks.get(ownerId);
+ if (blocks == null || !blocks.contains(location)) {
+ return false;
+ }
+
+ // 移除方块
+ blocks.remove(location);
+ location.getBlock().setType(Material.AIR);
+
+ // 生成破坏效果
+ spawnBreakEffects(location, breaker);
+
+ // 通知所有相关玩家
+ notifyBlockBreak(ownerId, breaker, blocks.size());
+
+ // 检查玩家是否还有剩余方块
+ if (blocks.isEmpty()) {
+ handlePlayerElimination(ownerId);
+ }
+
+ return true;
+ }
+
+ /**
+ * 生成破坏效果
+ */
+ private void spawnBreakEffects(Location location, Player breaker) {
+ if (location.getWorld() != null) {
+ // 粒子效果
+ location.getWorld().spawnParticle(org.bukkit.Particle.BLOCK_CRACK,
+ location.clone().add(0.5, 0.5, 0.5),
+ 50, 0.5, 0.5, 0.5, 0.5, location.getBlock().getBlockData());
+
+ location.getWorld().spawnParticle(org.bukkit.Particle.SMOKE_LARGE,
+ location.clone().add(0.5, 0.5, 0.5),
+ 20, 0.3, 0.3, 0.3, 0.05);
+
+ // 音效
+ location.getWorld().playSound(location, org.bukkit.Sound.ENTITY_ITEM_BREAK, 1.0f, 0.8f);
+ location.getWorld().playSound(location, org.bukkit.Sound.BLOCK_GLASS_BREAK, 0.8f, 1.0f);
+
+ // 对挖掘者造成轻微击退
+ if (breaker != null) {
+ Location breakerLoc = breaker.getLocation();
+ org.bukkit.util.Vector direction = location.toVector().subtract(breakerLoc.toVector()).normalize();
+ breaker.setVelocity(direction.multiply(-0.5).setY(0.3));
+ }
+ }
+ }
+
+ /**
+ * 通知方块被破坏
+ */
+ private void notifyBlockBreak(UUID ownerId, Player breaker, int remaining) {
+ Player owner = Bukkit.getPlayer(ownerId);
+ String ownerName = Bukkit.getOfflinePlayer(ownerId).getName();
+
+ // 通知方块所有者
+ if (owner != null && owner.isOnline()) {
+ owner.sendMessage(ChatColor.RED + "⚠ 警告!你的生命方块被破坏了!");
+ owner.sendMessage(ChatColor.GRAY + "破坏者: " + ChatColor.YELLOW + (breaker != null ? breaker.getName() : "未知"));
+ owner.sendMessage(ChatColor.GRAY + "剩余生命方块: " + ChatColor.GREEN + String.valueOf(remaining) + " " + ChatColor.GRAY + "/ " + ChatColor.RED + 5);
+
+ if (remaining <= 2) {
+ owner.sendMessage(ChatColor.DARK_RED + "⚠ 警告!生命方块即将耗尽!");
+ }
+
+ // 播放警告音效
+ owner.playSound(owner.getLocation(), org.bukkit.Sound.ENTITY_ENDERMAN_TELEPORT, 0.8f, 0.5f);
+ }
+
+ // 通知破坏者
+ if (breaker != null && !breaker.getUniqueId().equals(ownerId)) {
+ breaker.sendMessage(ChatColor.GOLD + "你破坏了一个生命方块!");
+ breaker.sendMessage(ChatColor.GRAY + "所有者: " + ChatColor.YELLOW + (ownerName != null ? ownerName : "未知玩家"));
+ breaker.sendMessage(ChatColor.GRAY + "对方剩余生命方块: " + ChatColor.GREEN + String.valueOf(remaining));
+
+ // 给予挖掘者经验奖励
+ breaker.giveExp(5);
+ breaker.playSound(breaker.getLocation(), org.bukkit.Sound.ENTITY_EXPERIENCE_ORB_PICKUP, 1.0f, 1.0f);
+ }
+
+ // 广播给附近玩家
+ org.bukkit.World world = owner != null ? owner.getWorld() : (breaker != null ? breaker.getWorld() : null);
+ if (world != null) {
+ for (Player nearby : world.getPlayers()) {
+ if (nearby != owner && nearby != breaker &&
+ nearby.getLocation().distance(owner != null ? owner.getLocation() : breaker.getLocation()) < 30) {
+ nearby.sendMessage(ChatColor.GRAY + "[附近] " + ChatColor.YELLOW + "一个生命方块被破坏了!");
+ }
+ }
+ }
+ }
+
+ /**
+ * 处理玩家淘汰
+ */
+ private void handlePlayerElimination(UUID playerId) {
+ Player player = Bukkit.getPlayer(playerId);
+ if (player == null || !player.isOnline()) {
+ return;
+ }
+
+ player.setGameMode(GameMode.SPECTATOR);
+ player.sendTitle(ChatColor.DARK_RED + "☠ 你被淘汰了!", ChatColor.RED + "所有生命方块已被挖光", 20, 100, 20);
+
+ // 从存活玩家列表中移除
+ alivePlayers.remove(playerId);
+
+ // 检查是否只剩一名玩家
+ checkGameEnd();
+ }
+
+ /**
+ * 检查游戏是否结束
+ */
+ public void checkGameEnd() {
+ // 如果限时游戏且时间到了
+ if (isLimitedTime && gameStartTime > 0) {
+ long currentTime = System.currentTimeMillis();
+ long elapsedMinutes = (currentTime - gameStartTime) / (1000 * 60);
+
+ if (elapsedMinutes >= gameDuration) {
+ endGame();
+ return;
+ }
+ }
+
+ // 如果只剩一名玩家存活
+ if (alivePlayers.size() <= 1) {
+ endGame();
+ }
+ }
+
+ /**
+ * 结束游戏
+ */
+ private void endGame() {
+ if (currentState != GameState.STARTED) {
+ return;
+ }
+
+ setCurrentState(GameState.FINISHED);
+
+ Player winner = null;
+ if (!alivePlayers.isEmpty()) {
+ winner = Bukkit.getPlayer(alivePlayers.get(0));
+ }
+
+ // 广播胜利消息
+ for (Player player : Bukkit.getOnlinePlayers()) {
+ if (winner != null) {
+ player.sendTitle(ChatColor.GOLD + "🎉 恭喜!", ChatColor.YELLOW + winner.getName() + " 获得了胜利!", 10, 70, 20);
+ player.sendMessage(ChatColor.GOLD + "🎉 游戏结束!" + ChatColor.YELLOW + winner.getName() + ChatColor.GOLD + " 获得了胜利!");
+ } else {
+ player.sendTitle(ChatColor.GOLD + "游戏结束!", ChatColor.YELLOW + "平局!", 10, 70, 20);
+ player.sendMessage(ChatColor.GOLD + "游戏结束!没有获胜者!");
+ }
+ }
+ }
+
+ /**
+ * 开始限时游戏
+ */
+ /**
+ * 开始限时游戏
+ *
+ * @param minutes 游戏限时(分钟)
+ */
+ public void startTimedGame(int minutes) {
+ this.gameDuration = minutes;
+ this.isLimitedTime = true;
+ startGame();
+ }
+
+ /**
+ * 开始游戏
+ */
+ public void startGame() {
+ if (currentState != GameState.WAITING) {
+ return;
+ }
+
+ setCurrentState(GameState.STARTED);
+
+ // 为所有玩家生成生命方块
+ for (UUID playerId : playerColors.keySet()) {
+ Player player = Bukkit.getPlayer(playerId);
+ if (player != null && player.isOnline() && !player.isOp()) { // 只为非OP玩家生成方块
+ generateLifeBlocksForPlayer(player);
+ }
+ }
+ }
+
+ /**
+ * 重置游戏
+ */
+ public void resetGame() {
+ // 清空所有数据
+ playerColors.clear();
+ playerBlocks.clear();
+ alivePlayers.clear();
+ gameDuration = 0;
+ isLimitedTime = false;
+ gameStartTime = 0;
+
+ // 将所有玩家恢复到适当模式,并重新分配颜色
+ for (Player player : Bukkit.getOnlinePlayers()) {
+ if (player.isOp()) {
+ player.setGameMode(GameMode.SPECTATOR);
+ } else {
+ player.setGameMode(GameMode.ADVENTURE);
+ player.getInventory().clear();
+ // 重新分配颜色
+ assignPlayerColor(player);
+ }
+ }
+
+ setCurrentState(GameState.WAITING);
+ }
+
+ /**
+ * 获取玩家剩余方块数量
+ */
+ /**
+ * 获取玩家剩余方块数量
+ *
+ * @param playerId 玩家UUID
+ * @return 玩家剩余方块数量
+ */
+ public int getRemainingBlocks(UUID playerId) {
+ List blocks = playerBlocks.get(playerId);
+ return blocks != null ? blocks.size() : 0;
+ }
+
+ /**
+ * 获取玩家颜色
+ */
+ /**
+ * 获取玩家颜色
+ *
+ * @param playerId 玩家UUID
+ * @return 玩家的颜色材料
+ */
+ public Material getPlayerColor(UUID playerId) {
+ return playerColors.get(playerId);
+ }
+
+ /**
+ * 更新计分板
+ */
+ public void updateScoreboard() {
+ if (gameObjective == null) {
+ return;
+ }
+
+ // 清空现有计分板条目
+ for (String entry : gameScoreboard.getEntries()) {
+ gameScoreboard.resetScores(entry);
+ }
+
+ // 设置标题
+ gameObjective.setDisplayName(ChatColor.GOLD + "PlayerBlockLife 游戏");
+
+ // 添加内容
+ int line = 1;
+ String statusText = "";
+ switch (currentState) {
+ case WAITING:
+ statusText = ChatColor.YELLOW + "等待中...";
+ break;
+ case STARTED:
+ statusText = ChatColor.GREEN + "开始游戏";
+ break;
+ case FINISHED:
+ statusText = ChatColor.RED + "游戏结束";
+ break;
+ }
+
+ gameScoreboard.getObjective(DisplaySlot.SIDEBAR).getScore(ChatColor.WHITE + "").setScore(line++);
+ gameScoreboard.getObjective(DisplaySlot.SIDEBAR).getScore(ChatColor.AQUA + "状态:").setScore(line++);
+ gameScoreboard.getObjective(DisplaySlot.SIDEBAR).getScore(statusText).setScore(line++);
+
+ // 如果是游戏中,显示剩余时间和玩家方块数
+ if (currentState == GameState.STARTED) {
+ gameScoreboard.getObjective(DisplaySlot.SIDEBAR).getScore(" ").setScore(line++);
+
+ // 显示剩余时间(如果有限时)
+ if (isLimitedTime) {
+ long currentTime = System.currentTimeMillis();
+ long elapsedMinutes = (currentTime - gameStartTime) / (1000 * 60);
+ long remainingMinutes = Math.max(0, gameDuration - elapsedMinutes);
+
+ gameScoreboard.getObjective(DisplaySlot.SIDEBAR).getScore(ChatColor.AQUA + "剩余时间:").setScore(line++);
+ gameScoreboard.getObjective(DisplaySlot.SIDEBAR).getScore(ChatColor.YELLOW + String.valueOf(remainingMinutes) + "分钟").setScore(line++);
+ gameScoreboard.getObjective(DisplaySlot.SIDEBAR).getScore(" ").setScore(line++);
+ }
+
+ // 显示当前玩家剩余方块数
+ gameScoreboard.getObjective(DisplaySlot.SIDEBAR).getScore(ChatColor.AQUA + "我的方块:").setScore(line++);
+ // 这个会在PlayerJoinListener中根据每个玩家更新
+ }
+
+ gameScoreboard.getObjective(DisplaySlot.SIDEBAR).getScore(" ").setScore(line++);
+ gameScoreboard.getObjective(DisplaySlot.SIDEBAR).getScore(ChatColor.GRAY + "PlayerBlockLife v" + plugin.getDescription().getVersion()).setScore(line++);
+
+ // 给在线玩家设置计分板
+ for (Player player : Bukkit.getOnlinePlayers()) {
+ player.setScoreboard(gameScoreboard);
+ }
+ }
+
+ /**
+ * 更新玩家特定的计分板信息
+ */
+ /**
+ * 更新玩家特定的计分板信息
+ *
+ * @param player 要更新计分板的玩家
+ */
+ public void updatePlayerScoreboard(Player player) {
+ if (currentState == GameState.STARTED && gameObjective != null) {
+ // 为特定玩家更新剩余方块数
+ int remainingBlocks = getRemainingBlocks(player.getUniqueId());
+ String blocksText = ChatColor.YELLOW + String.valueOf(remainingBlocks) + "/5";
+
+ // 临时更新计分板,为特定玩家显示他们的剩余方块
+ Scoreboard playerScoreboard = player.getScoreboard();
+ if (playerScoreboard != gameScoreboard) {
+ playerScoreboard = gameScoreboard;
+ }
+
+ // 为每个玩家创建一个自定义的计分板
+ Scoreboard customScoreboard = scoreboardManager.getNewScoreboard();
+ Objective customObjective = customScoreboard.registerNewObjective("pbl_player", "dummy", ChatColor.GOLD + "PlayerBlockLife 游戏");
+ customObjective.setDisplaySlot(DisplaySlot.SIDEBAR);
+
+ // 复制原始计分板内容
+ int line = 1;
+ String statusText = "";
+ switch (currentState) {
+ case WAITING:
+ statusText = ChatColor.YELLOW + "等待中...";
+ break;
+ case STARTED:
+ statusText = ChatColor.GREEN + "开始游戏";
+ break;
+ case FINISHED:
+ statusText = ChatColor.RED + "游戏结束";
+ break;
+ }
+
+ customScoreboard.getObjective(DisplaySlot.SIDEBAR).getScore(ChatColor.WHITE + "").setScore(line++);
+ customScoreboard.getObjective(DisplaySlot.SIDEBAR).getScore(ChatColor.AQUA + "状态:").setScore(line++);
+ customScoreboard.getObjective(DisplaySlot.SIDEBAR).getScore(statusText).setScore(line++);
+
+ if (isLimitedTime) {
+ long currentTime = System.currentTimeMillis();
+ long elapsedMinutes = (currentTime - gameStartTime) / (1000 * 60);
+ long remainingMinutes = Math.max(0, gameDuration - elapsedMinutes);
+
+ customScoreboard.getObjective(DisplaySlot.SIDEBAR).getScore(" ").setScore(line++);
+ customScoreboard.getObjective(DisplaySlot.SIDEBAR).getScore(ChatColor.AQUA + "剩余时间:").setScore(line++);
+ customScoreboard.getObjective(DisplaySlot.SIDEBAR).getScore(ChatColor.YELLOW + String.valueOf(remainingMinutes) + "分钟").setScore(line++);
+ }
+
+ customScoreboard.getObjective(DisplaySlot.SIDEBAR).getScore(" ").setScore(line++);
+ customScoreboard.getObjective(DisplaySlot.SIDEBAR).getScore(ChatColor.AQUA + "我的方块:").setScore(line++);
+ customScoreboard.getObjective(DisplaySlot.SIDEBAR).getScore(blocksText).setScore(line++);
+
+ customScoreboard.getObjective(DisplaySlot.SIDEBAR).getScore(" ").setScore(line++);
+ customScoreboard.getObjective(DisplaySlot.SIDEBAR).getScore(ChatColor.GRAY + "PlayerBlockLife v" + plugin.getDescription().getVersion()).setScore(line++);
+
+ player.setScoreboard(customScoreboard);
+ }
+ }
+
+ /**
+ * 获取存活玩家数量
+ */
+ /**
+ * 获取存活玩家数量
+ *
+ * @return 存活玩家数量
+ */
+ public int getAlivePlayersCount() {
+ return alivePlayers.size();
+ }
+
+ /**
+ * 获取所有存活玩家
+ */
+ /**
+ * 获取所有存活玩家
+ *
+ * @return 存活玩家UUID列表
+ */
+ public List getAlivePlayers() {
+ return new ArrayList<>(alivePlayers);
+ }
+
+ /**
+ * 获取所有玩家的方块位置
+ */
+ /**
+ * 获取所有玩家的方块位置
+ *
+ * @return 玩家UUID到方块位置列表的映射
+ */
+ public Map> getPlayerBlocks() {
+ return new HashMap<>(playerBlocks);
+ }
+
+ /**
+ * 清除指定玩家的方块
+ */
+ /**
+ * 清除指定玩家的方块
+ *
+ * @param playerId 要清除方块的玩家UUID
+ */
+ public void clearPlayerBlocks(UUID playerId) {
+ List blocks = playerBlocks.remove(playerId);
+ if (blocks != null) {
+ for (Location loc : blocks) {
+ if (loc.getWorld() != null) {
+ loc.getBlock().setType(org.bukkit.Material.AIR);
+ }
+ }
+ }
+
+ // 从存活玩家列表中移除
+ alivePlayers.remove(playerId);
+ }
+
+ /**
+ * 获取游戏持续时间(分钟)
+ */
+ /**
+ * 获取游戏持续时间(分钟)
+ *
+ * @return 游戏持续时间(分钟)
+ */
+ public int getGameDuration() {
+ return gameDuration;
+ }
+
+ /**
+ * 获取游戏开始时间戳
+ */
+ /**
+ * 获取游戏开始时间戳
+ *
+ * @return 游戏开始时间戳
+ */
+ public long getGameStartTime() {
+ return gameStartTime;
+ }
+
+ /**
+ * 检查是否为限时游戏
+ */
+ /**
+ * 检查是否为限时游戏
+ *
+ * @return 如果是限时游戏返回true,否则返回false
+ */
+ public boolean isLimitedTime() {
+ return isLimitedTime;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/playerblocklife/LifeSystem.java b/src/main/java/com/playerblocklife/LifeSystem.java
index 425f5ed..9589b60 100644
--- a/src/main/java/com/playerblocklife/LifeSystem.java
+++ b/src/main/java/com/playerblocklife/LifeSystem.java
@@ -33,7 +33,7 @@ import java.util.UUID;
*
*
* @author xiaobai
- * @version 2.1.0
+ * @version 2.2.0
* @since 1.0.0
*/
public class LifeSystem {
@@ -46,8 +46,14 @@ public class LifeSystem {
}
public void checkAllPlayers() {
- for (Player player : Bukkit.getOnlinePlayers()) {
- checkPlayerHealth(player);
+ // 只在游戏进行中时检查玩家生命值,等待和旁观状态的玩家不检查
+ if (plugin.getGameStateManager().getCurrentState() == GameStateManager.GameState.STARTED) {
+ for (Player player : Bukkit.getOnlinePlayers()) {
+ // 只检查生存模式的玩家(非旁观者)
+ if (player.getGameMode() == GameMode.SURVIVAL) {
+ checkPlayerHealth(player);
+ }
+ }
}
}
diff --git a/src/main/java/com/playerblocklife/MessageManager.java b/src/main/java/com/playerblocklife/MessageManager.java
index 36f6558..220fbb0 100644
--- a/src/main/java/com/playerblocklife/MessageManager.java
+++ b/src/main/java/com/playerblocklife/MessageManager.java
@@ -16,6 +16,11 @@ public class MessageManager {
private File messageFile;
private final Map messageCache = new HashMap<>();
+ /**
+ * 构造一个新的消息管理器
+ *
+ * @param plugin 插件主类实例
+ */
public MessageManager(PlayerBlockLife plugin) {
this.plugin = plugin;
this.messageFile = new File(plugin.getDataFolder(), "messages.yml");
@@ -86,6 +91,13 @@ public class MessageManager {
/**
* 获取消息
*/
+ /**
+ * 获取消息
+ *
+ * @param path 消息路径
+ * @param defaultValue 默认值
+ * @return 消息内容
+ */
public String getMessage(String path, String defaultValue) {
ConfigManager config = plugin.getConfigManager();
@@ -122,6 +134,14 @@ public class MessageManager {
/**
* 获取格式化消息(替换变量)
*/
+ /**
+ * 获取格式化消息(替换变量)
+ *
+ * @param path 消息路径
+ * @param defaultValue 默认值
+ * @param variables 变量映射
+ * @return 格式化后的消息内容
+ */
public String getFormattedMessage(String path, String defaultValue, Map variables) {
String message = getMessage(path, defaultValue);
@@ -137,6 +157,13 @@ public class MessageManager {
/**
* 获取控制台消息
*/
+ /**
+ * 获取控制台消息
+ *
+ * @param path 消息路径
+ * @param defaultValue 默认值
+ * @return 控制台消息内容
+ */
public String getConsoleMessage(String path, String defaultValue) {
String message = getMessage("console." + path, defaultValue);
// 移除颜色代码(控制台不需要)
@@ -149,6 +176,13 @@ public class MessageManager {
/**
* 获取游戏内消息
*/
+ /**
+ * 获取游戏内消息
+ *
+ * @param path 消息路径
+ * @param defaultValue 默认值
+ * @return 游戏内消息内容
+ */
public String getGameMessage(String path, String defaultValue) {
return getMessage("game." + path, defaultValue);
}
@@ -156,6 +190,14 @@ public class MessageManager {
/**
* 获取命令消息
*/
+ /**
+ * 获取命令消息
+ *
+ * @param command 命令名称
+ * @param path 消息路径
+ * @param defaultValue 默认值
+ * @return 命令消息内容
+ */
public String getCommandMessage(String command, String path, String defaultValue) {
return getMessage("commands." + command + "." + path, defaultValue);
}
@@ -163,6 +205,13 @@ public class MessageManager {
/**
* 获取广播消息
*/
+ /**
+ * 获取广播消息
+ *
+ * @param path 消息路径
+ * @param defaultValue 默认值
+ * @return 广播消息内容
+ */
public String getBroadcastMessage(String path, String defaultValue) {
return getMessage("broadcast." + path, defaultValue);
}
@@ -170,6 +219,11 @@ public class MessageManager {
/**
* 检查消息文件是否存在
*/
+ /**
+ * 检查消息文件是否存在
+ *
+ * @return 如果外部消息文件存在则返回true,否则返回false
+ */
public boolean hasExternalMessageFile() {
return messageFile.exists();
}
@@ -177,6 +231,11 @@ public class MessageManager {
/**
* 获取消息文件路径
*/
+ /**
+ * 获取消息文件路径
+ *
+ * @return 消息文件的绝对路径
+ */
public String getMessageFilePath() {
return messageFile.getAbsolutePath();
}
diff --git a/src/main/java/com/playerblocklife/PBLCommands.java b/src/main/java/com/playerblocklife/PBLCommands.java
new file mode 100644
index 0000000..639dd09
--- /dev/null
+++ b/src/main/java/com/playerblocklife/PBLCommands.java
@@ -0,0 +1,139 @@
+package com.playerblocklife;
+
+import org.bukkit.ChatColor;
+import org.bukkit.command.Command;
+import org.bukkit.command.CommandExecutor;
+import org.bukkit.command.CommandSender;
+import org.bukkit.entity.Player;
+
+/**
+ * PlayerBlockLife命令执行器 - 处理PlayerBlockLife游戏模式的专用命令
+ *
+ * 提供PlayerBlockLife游戏模式的核心控制命令,包括:
+ *
+ * /PlayerBlockLife start [时间] - 开始游戏,支持可选的限时模式
+ * /PlayerBlockLife rstgm - 重置游戏,重新分配玩家颜色
+ *
+ *
+ * 这些命令仅允许服务器管理员(OP)执行,用于控制PlayerBlockLife游戏的生命周期。
+ *
+ * @author xiaobai
+ * @version 2.2.0-1.20.4
+ * @since 4.0.0
+ */
+public class PBLCommands implements CommandExecutor {
+ private final PlayerBlockLife plugin;
+
+ public PBLCommands(PlayerBlockLife plugin) {
+ this.plugin = plugin;
+ }
+
+ @Override
+ public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
+ if (!(sender instanceof Player) && !sender.hasPermission("playerblocklife.admin")) {
+ // 控制台默认有权限
+ if (!sender.isOp() && !sender.hasPermission("playerblocklife.admin")) {
+ sender.sendMessage(ChatColor.RED + "你没有权限执行此命令!");
+ return true;
+ }
+ } else if (sender instanceof Player) {
+ Player player = (Player) sender;
+ if (!player.isOp() && !player.hasPermission("playerblocklife.admin")) {
+ player.sendMessage(ChatColor.RED + "你没有权限执行此命令!");
+ return true;
+ }
+ }
+
+ if (args.length == 0) {
+ if (sender instanceof Player) {
+ showHelp((Player) sender);
+ } else {
+ showConsoleHelp(sender);
+ }
+ return true;
+ }
+
+ String subCommand = args[0].toLowerCase();
+
+ if (subCommand.equals("start")) {
+ handleStartCommand(sender, args);
+ } else if (subCommand.equals("rstgm") || subCommand.equals("reset")) {
+ handleResetCommand(sender);
+ } else {
+ if (sender instanceof Player) {
+ showHelp((Player) sender);
+ } else {
+ showConsoleHelp(sender);
+ }
+ }
+
+ return true;
+ }
+
+ private void handleStartCommand(CommandSender sender, String[] args) {
+ if (plugin.getGameStateManager().getCurrentState() == GameStateManager.GameState.STARTED) {
+ sender.sendMessage(ChatColor.RED + "游戏已在进行中!");
+ return;
+ }
+
+ int duration = 0;
+ boolean isTimed = false;
+
+ // 检查是否有时间参数
+ if (args.length > 1) {
+ try {
+ duration = Integer.parseInt(args[1]);
+ if (duration <= 0) {
+ sender.sendMessage(ChatColor.RED + "游戏时间必须大于0分钟!");
+ return;
+ }
+ isTimed = true;
+ sender.sendMessage(ChatColor.GREEN + "限时游戏开始,持续时间: " + duration + " 分钟!");
+ } catch (NumberFormatException e) {
+ sender.sendMessage(ChatColor.RED + "无效的时间参数!请输入分钟数,例如: /pbl start 30");
+ return;
+ }
+ } else {
+ sender.sendMessage(ChatColor.GREEN + "游戏开始!");
+ }
+
+ // 开始游戏
+ if (isTimed) {
+ plugin.getGameStateManager().startTimedGame(duration);
+ } else {
+ plugin.getGameStateManager().startGame();
+ }
+
+ // 广播游戏开始消息
+ for (Player onlinePlayer : org.bukkit.Bukkit.getOnlinePlayers()) {
+ onlinePlayer.sendMessage(ChatColor.GOLD + "游戏开始!保护好你的生命方块!");
+ if (isTimed) {
+ onlinePlayer.sendMessage(ChatColor.YELLOW + "限时: " + duration + " 分钟");
+ }
+ }
+ }
+
+ private void handleResetCommand(CommandSender sender) {
+ plugin.getGameStateManager().resetGame();
+ sender.sendMessage(ChatColor.YELLOW + "游戏已重置,所有玩家回到等待状态。");
+
+ // 广播重置消息
+ for (Player onlinePlayer : org.bukkit.Bukkit.getOnlinePlayers()) {
+ onlinePlayer.sendMessage(ChatColor.GOLD + "游戏已重置,请等待重新分配颜色。");
+ }
+ }
+
+ private void showHelp(Player player) {
+ player.sendMessage(ChatColor.GOLD + "=== PlayerBlockLife 命令 ===");
+ player.sendMessage(ChatColor.YELLOW + "/pbl start [时间(分钟)] - 开始游戏,可选限时");
+ player.sendMessage(ChatColor.YELLOW + "/pbl rstgm - 重置游戏");
+ player.sendMessage(ChatColor.GRAY + "注:只有管理员(权限)可以执行这些命令");
+ }
+
+ private void showConsoleHelp(CommandSender sender) {
+ sender.sendMessage(ChatColor.GOLD + "=== PlayerBlockLife 命令 ===");
+ sender.sendMessage(ChatColor.YELLOW + "/pbl start [时间(分钟)] - 开始游戏,可选限时");
+ sender.sendMessage(ChatColor.YELLOW + "/pbl rstgm - 重置游戏");
+ sender.sendMessage(ChatColor.GRAY + "注:控制台拥有执行这些命令的权限");
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/playerblocklife/PlayerBlockLife.java b/src/main/java/com/playerblocklife/PlayerBlockLife.java
index b72827d..a3d4263 100644
--- a/src/main/java/com/playerblocklife/PlayerBlockLife.java
+++ b/src/main/java/com/playerblocklife/PlayerBlockLife.java
@@ -6,22 +6,25 @@ import org.bukkit.plugin.java.JavaPlugin;
import java.util.logging.Level;
/**
- * PlayerBlockLife插件主类 - 玩家生命方块系统的核心控制器
+ * PlayerBlockLife插件主类 - PlayerBlockLife生存游戏模式的核心控制器
*
- * 这个插件为Minecraft服务器添加了一个独特的游戏机制:每个玩家拥有一定数量的生命方块,
- * 这些方块使用玩家的皮肤作为材质。当其他玩家挖光某个玩家的所有生命方块时,该玩家会被淘汰。
+ * 这个插件为Minecraft服务器添加了一个独特的生存游戏模式:每个非OP玩家拥有一定数量的生命方块,
+ * 这些方块使用不同颜色的羊毛、玻璃或水泥方块表示。当其他玩家挖光某个玩家的所有生命方块时,该玩家会被淘汰。
+ * 游戏需要管理员使用/pbl start命令开始,支持限时模式,最后存活的玩家获胜。
*
- * 主要功能:
+ *
主要功能:
*
- * 管理玩家生命方块的生成和销毁
- * 处理玩家皮肤的获取和应用
- * 监控玩家生命值状态
- * 提供完整的命令和权限系统
- * 支持配置热重载和数据持久化
+ * 管理PlayerBlockLife游戏的完整生命周期(等待、进行、结束)
+ * 为非OP玩家分配独特的颜色生命方块
+ * 处理生命方块的生成和销毁
+ * 监控游戏状态和玩家存活情况
+ * 提供PlayerBlockLife专用命令系统(/pbl start, /pbl rstgm)
+ * 支持游戏计分板显示
+ * 管理员可使用传统命令进行管理
*
*
* @author xiaobai
- * @version 3.0.0-experimental-1.20.4
+ * @version 2.2.0-1.20.4
* @since 1.0.0
*/
public class PlayerBlockLife extends JavaPlugin {
@@ -31,6 +34,7 @@ public class PlayerBlockLife extends JavaPlugin {
private LifeSystem lifeSystem;
private ConfigManager configManager;
private MessageManager messageManager;
+ private GameStateManager gameStateManager;
/**
* 插件启用时调用,执行初始化操作
@@ -78,12 +82,16 @@ public class PlayerBlockLife extends JavaPlugin {
getCommand("pbldelete").setExecutor(new AdminCommands(this));
getCommand("pblrevive").setExecutor(new AdminCommands(this));
getCommand("pblstats").setExecutor(new AdminCommands(this));
+ getCommand("pbl").setExecutor(new PBLCommands(this));
- // 第六步:加载其他数据
- blockManager.loadData();
- skinManager.loadAllSkins();
+ // 第六步:初始化游戏状态管理器(先于其他数据加载)
+ this.gameStateManager = new GameStateManager(this);
- // 第七步:启动定时任务
+ // 第七步:加载其他数据
+ // 注意:在新模式下,我们不再使用原有的blockManager和skinManager
+ // 因此不再调用blockManager.loadData()和skinManager.loadAllSkins()
+
+ // 第八步:启动定时任务
startScheduler();
getLogger().info("§a========================================");
@@ -172,6 +180,10 @@ public class PlayerBlockLife extends JavaPlugin {
if (lifeSystem != null) {
lifeSystem.checkAllPlayers();
}
+ // 每10秒检查游戏结束条件
+ if (gameStateManager != null) {
+ gameStateManager.checkGameEnd();
+ }
}, 200L, 200L);
// 每分钟清理一次过期缓存
@@ -180,6 +192,13 @@ public class PlayerBlockLife extends JavaPlugin {
skinManager.cleanupOldCache();
}
}, 1200L, 1200L);
+
+ // 每秒更新计分板
+ getServer().getScheduler().runTaskTimer(this, () -> {
+ if (gameStateManager != null) {
+ gameStateManager.updateScoreboard();
+ }
+ }, 20L, 20L);
}
/**
@@ -194,34 +213,84 @@ public class PlayerBlockLife extends JavaPlugin {
return instance;
}
+ /**
+ * 获取方块管理器
+ *
+ * @return 方块管理器实例
+ */
public PlayerBlockManager getBlockManager() {
return blockManager;
}
+ /**
+ * 获取皮肤管理器
+ *
+ * @return 皮肤管理器实例
+ */
public SkinManager getSkinManager() {
return skinManager;
}
+ /**
+ * 获取生命值系统
+ *
+ * @return 生命值系统实例
+ */
public LifeSystem getLifeSystem() {
return lifeSystem;
}
+ /**
+ * 获取配置管理器
+ *
+ * @return 配置管理器实例
+ */
public ConfigManager getConfigManager() {
return configManager;
}
+ /**
+ * 获取消息管理器
+ *
+ * @return 消息管理器实例
+ */
public MessageManager getMessageManager() {
return messageManager;
}
+ /**
+ * 获取游戏状态管理器
+ *
+ * @return 游戏状态管理器实例
+ */
+ public GameStateManager getGameStateManager() {
+ return gameStateManager;
+ }
+
+ /**
+ * 记录信息级别日志
+ *
+ * @param message 日志消息
+ */
public void logInfo(String message) {
getLogger().info(message);
}
+ /**
+ * 记录警告级别日志
+ *
+ * @param message 日志消息
+ */
public void logWarning(String message) {
getLogger().warning(message);
}
+ /**
+ * 记录错误级别日志
+ *
+ * @param message 日志消息
+ * @param throwable 异常对象
+ */
public void logError(String message, Throwable throwable) {
getLogger().log(Level.SEVERE, message, throwable);
}
diff --git a/src/main/java/com/playerblocklife/PlayerBlockManager.java b/src/main/java/com/playerblocklife/PlayerBlockManager.java
index 01ac9be..81874e2 100644
--- a/src/main/java/com/playerblocklife/PlayerBlockManager.java
+++ b/src/main/java/com/playerblocklife/PlayerBlockManager.java
@@ -38,12 +38,11 @@ import java.util.concurrent.ConcurrentHashMap;
* 支持异步皮肤加载,避免方块放置阻塞
* 提供皮肤加载状态检查,确保皮肤就绪后再放置方块
*
- *
*
* 使用并发安全的数据结构确保多线程环境下的数据一致性。
*
* @author xiaobai
- * @version 2.1.0
+ * @version 2.2.0
* @since 1.0.0
*/
public class PlayerBlockManager {
@@ -66,12 +65,10 @@ public class PlayerBlockManager {
*/
public boolean setLifeBlocks(Player player, Location center) {
ConfigManager config = plugin.getConfigManager();
- int blockAmount = config.getBlocksPerPlayer();
- int spreadRange = config.getSpreadRange();
- boolean requireOpenSky = config.isRequireOpenSky();
- int maxAttempts = config.getMaxAttempts();
+ boolean requireOpenSky = true; // 使用默认值,因为配置已移除
+ int maxAttempts = 50; // 使用默认值,因为配置已移除
- return generateLifeBlocksForPlayer(player, blockAmount, spreadRange, requireOpenSky, maxAttempts);
+ return generateLifeBlocksForPlayer(player, 5, 5, requireOpenSky, maxAttempts);
}
/**
diff --git a/src/main/java/com/playerblocklife/PlayerJoinListener.java b/src/main/java/com/playerblocklife/PlayerJoinListener.java
index 1f54444..58198d4 100644
--- a/src/main/java/com/playerblocklife/PlayerJoinListener.java
+++ b/src/main/java/com/playerblocklife/PlayerJoinListener.java
@@ -13,9 +13,32 @@ import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
+/**
+ * PlayerJoinListener - PlayerBlockLife游戏模式下的玩家加入和重生监听器
+ *
+ * 处理玩家加入服务器和重生时的逻辑,包括:
+ *
+ * 根据玩家权限设置游戏模式(OP玩家为观察者模式,非OP玩家为冒险模式)
+ * 为非OP玩家分配独特的生命方块颜色
+ * 给非OP玩家提供对应颜色的方块作为手持物品
+ * 根据当前游戏状态显示相应信息
+ * 处理玩家重生时的游戏状态恢复
+ *
+ *
+ * 此监听器与GameStateManager协作,确保玩家正确加入PlayerBlockLife游戏模式。
+ *
+ * @author xiaobai
+ * @version 2.2.0-1.20.4
+ * @since 1.0.0
+ */
public class PlayerJoinListener implements Listener {
private final PlayerBlockLife plugin;
+ /**
+ * 构造一个新的玩家加入监听器
+ *
+ * @param plugin 插件主类实例
+ */
public PlayerJoinListener(PlayerBlockLife plugin) {
this.plugin = plugin;
}
@@ -25,70 +48,34 @@ public class PlayerJoinListener implements Listener {
Player player = event.getPlayer();
UUID playerId = player.getUniqueId();
- // 异步加载玩家皮肤
- plugin.getSkinManager().loadPlayerSkinAsync(player);
+ // 根据玩家权限设置游戏模式
+ if (player.isOp()) {
+ // OP玩家设置为观察者模式
+ player.setGameMode(GameMode.SPECTATOR);
+ player.sendMessage(org.bukkit.ChatColor.YELLOW + "您是管理员,已设置为观察者模式。");
+ } else {
+ // 非OP玩家设置为冒险模式
+ player.setGameMode(GameMode.ADVENTURE);
+
+ // 分配颜色并给玩家对应颜色的方块
+ plugin.getGameStateManager().assignPlayerColor(player);
+ }
- // 延迟执行,确保皮肤加载完成
+ // 延迟执行,确保颜色分配完成
Bukkit.getScheduler().runTaskLater(plugin, () -> {
- int remainingBlocks = plugin.getBlockManager().getRemainingBlocks(playerId);
-
- // 检查是否启用自动生成
- if (plugin.getConfigManager().isAutoGenerationEnabled() && remainingBlocks == 0) {
- // 自动生成生命方块
- generateLifeBlocksForPlayer(player);
- } else if (remainingBlocks > 0) {
- // 已有方块,显示欢迎信息
- Map variables = new HashMap<>();
- variables.put("player", player.getName());
- variables.put("remaining", String.valueOf(remainingBlocks));
- variables.put("total", String.valueOf(plugin.getConfigManager().getBlocksPerPlayer()));
-
- String welcomeMsg = plugin.getMessageManager().getFormattedMessage("game.player.welcome_back",
- "&e欢迎回来,&6{player}&e!", variables);
- String blocksMsg = plugin.getMessageManager().getFormattedMessage("game.player.remaining_blocks",
- "&7你还有 &e{remaining} &7个生命方块", variables);
- String checkMsg = plugin.getMessageManager().getMessage("game.player.check_blocks_hint",
- "&7使用 &e/checklifeblocks &7查看方块位置");
-
- player.sendMessage("§a========================================");
- player.sendMessage(welcomeMsg);
- player.sendMessage(blocksMsg);
- player.sendMessage(checkMsg);
- player.sendMessage("§a========================================");
-
- if (remainingBlocks <= 2) {
- String warningMsg = plugin.getMessageManager().getMessage("game.block.warning_low_blocks",
- "&c⚠ 警告!你的生命方块即将耗尽!");
- player.sendMessage(warningMsg);
- player.playSound(player.getLocation(),
- org.bukkit.Sound.ENTITY_WITHER_SPAWN, 0.5f, 1.0f);
- }
- } else {
- // 没有方块且自动生成未启用
- String welcomeMsg = plugin.getMessageManager().getMessage("game.player.welcome_new",
- "&e欢迎加入游戏!");
- String setBlocksMsg = plugin.getMessageManager().getMessage("game.player.set_blocks_hint",
- "&7使用 &e/setlifeblocks &7来设置你的生命方块");
- String rulesTitle = plugin.getMessageManager().getMessage("game.player.rules_title",
- "&6游戏规则:");
- String rule1 = plugin.getMessageManager().getMessage("game.player.rule1",
- "&7- 每个玩家有5个生命方块");
- String rule2 = plugin.getMessageManager().getMessage("game.player.rule2",
- "&7- 方块被其他玩家挖光时,你将死亡");
- String rule3 = plugin.getMessageManager().getMessage("game.player.rule3",
- "&7- 方块使用你的皮肤作为材质");
- String rule4 = plugin.getMessageManager().getMessage("game.player.rule4",
- "&7- 你可以自由移动,但方块固定位置");
-
- player.sendMessage(welcomeMsg);
- player.sendMessage(setBlocksMsg);
- player.sendMessage(rulesTitle);
- player.sendMessage(rule1);
- player.sendMessage(rule2);
- player.sendMessage(rule3);
- player.sendMessage(rule4);
+ // 更新玩家的计分板
+ plugin.getGameStateManager().updatePlayerScoreboard(player);
+
+ // 根据游戏状态显示不同信息
+ GameStateManager.GameState currentState = plugin.getGameStateManager().getCurrentState();
+ if (currentState == GameStateManager.GameState.WAITING) {
+ player.sendMessage(org.bukkit.ChatColor.YELLOW + "游戏等待中,请等待管理员开始游戏。");
+ } else if (currentState == GameStateManager.GameState.STARTED) {
+ player.sendMessage(org.bukkit.ChatColor.GREEN + "游戏中,请小心保护你的生命方块!");
+ } else if (currentState == GameStateManager.GameState.FINISHED) {
+ player.sendMessage(org.bukkit.ChatColor.RED + "游戏已结束,请等待下一轮开始。");
}
- }, 40L);
+ }, 20L);
}
/**
@@ -98,14 +85,12 @@ public class PlayerJoinListener implements Listener {
try {
// 获取配置
ConfigManager config = plugin.getConfigManager();
- int blockAmount = config.getBlocksPerPlayer();
- int spreadRange = config.getSpreadRange();
- boolean requireOpenSky = config.isRequireOpenSky();
- int maxAttempts = config.getMaxAttempts();
+ boolean requireOpenSky = true; // 使用默认值,因为配置已移除
+ int maxAttempts = 50; // 使用默认值,因为配置已移除
// 调用方块管理器生成方块
boolean success = plugin.getBlockManager().generateLifeBlocksForPlayer(
- player, blockAmount, spreadRange, requireOpenSky, maxAttempts
+ player, 5, 5, requireOpenSky, maxAttempts
);
if (success) {
@@ -113,7 +98,7 @@ public class PlayerJoinListener implements Listener {
String message = plugin.getMessageManager().getMessage("console.blocks_generated",
"&a[PlayerBlockLife] 已为玩家 {player} 生成 {amount} 个生命方块");
message = message.replace("{player}", player.getName())
- .replace("{amount}", String.valueOf(blockAmount));
+ .replace("{amount}", String.valueOf(5)); // 使用固定值5
// 移除颜色代码用于日志
String logMessage = message.replace("&", "");
plugin.getLogger().info(logMessage);
@@ -121,7 +106,7 @@ public class PlayerJoinListener implements Listener {
// 发送给玩家
String playerMsg = plugin.getMessageManager().getMessage("game.block.placed",
"&a已为你生成 {amount} 个生命方块!");
- playerMsg = playerMsg.replace("{amount}", String.valueOf(blockAmount))
+ playerMsg = playerMsg.replace("{amount}", String.valueOf(5)) // 使用固定值5
.replace("&", "§");
player.sendMessage(playerMsg);
} else {
@@ -134,7 +119,7 @@ public class PlayerJoinListener implements Listener {
plugin.getLogger().warning(logFailureMsg);
// 根据配置处理失败
- String onFailure = config.getOnFailureAction();
+ String onFailure = "notify"; // 使用默认值,因为配置已移除
if (onFailure.equals("notify")) {
String notifyMsg = plugin.getMessageManager().getMessage("game.errors.cannot_generate_blocks",
"&c无法生成生命方块:{reason}");
@@ -168,14 +153,28 @@ public class PlayerJoinListener implements Listener {
Player player = event.getPlayer();
UUID playerId = player.getUniqueId();
- if (!plugin.getLifeSystem().isPlayerAlive(playerId)) {
- Bukkit.getScheduler().runTaskLater(plugin, () -> {
- if (player.isOnline()) {
- player.setGameMode(GameMode.SPECTATOR);
- player.sendMessage("§e你已被淘汰,可以观察其他玩家。");
- player.sendMessage("§7等待下一轮游戏开始...");
- }
- }, 20L);
+ // 根据当前游戏状态处理玩家重生
+ GameStateManager.GameState currentState = plugin.getGameStateManager().getCurrentState();
+ if (currentState == GameStateManager.GameState.STARTED) {
+ // 如果游戏中,检查玩家是否存活
+ int remainingBlocks = plugin.getGameStateManager().getRemainingBlocks(playerId);
+ if (remainingBlocks <= 0) {
+ // 玩家已被淘汰,设置为观察者模式
+ Bukkit.getScheduler().runTaskLater(plugin, () -> {
+ if (player.isOnline()) {
+ player.setGameMode(GameMode.SPECTATOR);
+ player.sendMessage(org.bukkit.ChatColor.YELLOW + "你已被淘汰,可以观察其他玩家。");
+ }
+ }, 20L);
+ }
+ } else {
+ // 如果游戏未开始或已结束,OP玩家保持观察者模式,非OP玩家设置为冒险模式
+ if (player.isOp()) {
+ player.setGameMode(GameMode.SPECTATOR);
+ } else {
+ player.setGameMode(GameMode.ADVENTURE);
+ plugin.getGameStateManager().assignPlayerColor(player);
+ }
}
}
}
\ No newline at end of file
diff --git a/src/main/java/com/playerblocklife/PlayerQuitListener.java b/src/main/java/com/playerblocklife/PlayerQuitListener.java
index 2781dee..3a4ba72 100644
--- a/src/main/java/com/playerblocklife/PlayerQuitListener.java
+++ b/src/main/java/com/playerblocklife/PlayerQuitListener.java
@@ -4,6 +4,16 @@ import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerQuitEvent;
+/**
+ * PlayerQuitListener - PlayerBlockLife游戏模式下的玩家退出监听器
+ *
+ * 处理玩家退出服务器时的日志记录。在PlayerBlockLife游戏模式中,玩家退出不会影响
+ * 其生命方块状态,因为方块位置信息仅在服务器运行期间维护。
+ *
+ * @author xiaobai
+ * @version 2.2.0-1.20.4
+ * @since 1.0.0
+ */
public class PlayerQuitListener implements Listener {
private final PlayerBlockLife plugin;
diff --git a/src/main/java/com/playerblocklife/SetLifeBlocksCommand.java b/src/main/java/com/playerblocklife/SetLifeBlocksCommand.java
index 810df7b..58bbed0 100644
--- a/src/main/java/com/playerblocklife/SetLifeBlocksCommand.java
+++ b/src/main/java/com/playerblocklife/SetLifeBlocksCommand.java
@@ -1,14 +1,22 @@
package com.playerblocklife;
-import org.bukkit.Bukkit;
-import org.bukkit.Location;
+import org.bukkit.ChatColor;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
-import java.util.UUID;
-
+/**
+ * SetLifeBlocksCommand - 在新模式下提示命令不可用
+ *
+ * 在PlayerBlockLife游戏模式下,此命令不再用于手动设置生命方块。
+ * 生命方块将在游戏开始时自动为每个非OP玩家生成。
+ * 玩家应等待管理员使用 /PlayerBlockLife start 命令开始游戏。
+ *
+ * @author xiaobai
+ * @version 2.2.0-1.20.4
+ * @since 1.0.0
+ */
public class SetLifeBlocksCommand implements CommandExecutor {
private final PlayerBlockLife plugin;
@@ -18,167 +26,18 @@ public class SetLifeBlocksCommand implements CommandExecutor {
@Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
- // 检查命令是否启用
- if (!plugin.getConfigManager().isCommandEnabled("setlifeblocks")) {
- String message = plugin.getMessageManager().getMessage("game.errors.command_disabled",
- "&c此命令已被禁用!");
- sender.sendMessage(message);
- return true;
- }
-
if (!(sender instanceof Player)) {
- String message = plugin.getMessageManager().getMessage("game.errors.no_permission",
- "&c只有玩家可以使用此命令!");
- sender.sendMessage(message);
+ sender.sendMessage(ChatColor.RED + "此命令只能由玩家执行!控制台无法执行此命令。");
return true;
}
Player player = (Player) sender;
- UUID playerId = player.getUniqueId();
- if (args.length > 0 && args[0].equalsIgnoreCase("help")) {
- showHelp(player);
- return true;
- }
-
- // 检查管理员权限和配置
- if (args.length > 0 && args[0].equalsIgnoreCase("other")) {
- // 检查是否允许管理员使用
- if (!plugin.getConfigManager().isAdminUseAllowed("setlifeblocks") || !player.hasPermission("playerblocklife.admin")) {
- String message = plugin.getMessageManager().getMessage("game.errors.no_permission",
- "&c你没有权限使用此命令!");
- player.sendMessage(message);
- return true;
- }
-
- if (args.length < 2) {
- String usage = plugin.getMessageManager().getCommandMessage("setlifeblocks", "usage",
- "&c用法: /setlifeblocks [reset|other|help]");
- player.sendMessage(usage);
- return true;
- }
-
- Player target = Bukkit.getPlayer(args[1]);
- if (target == null) {
- String message = plugin.getMessageManager().getMessage("game.errors.player_not_found",
- "&c找不到玩家: {player}");
- message = message.replace("{player}", args[1]);
- player.sendMessage(message);
- return true;
- }
-
- setBlocksForPlayer(target, player);
- return true;
- }
+ // 在新模式下,setlifeblocks命令不可用
+ player.sendMessage(ChatColor.RED + "此命令在新模式下不可用。");
+ player.sendMessage(ChatColor.YELLOW + "生命方块将在游戏开始时自动分配。");
+ player.sendMessage(ChatColor.GRAY + "请等待管理员使用 /PlayerBlockLife start 开始游戏。");
- // 检查是否允许玩家自己使用
- if (!plugin.getConfigManager().isSelfUseAllowed("setlifeblocks")) {
- String message = plugin.getMessageManager().getMessage("game.errors.no_permission",
- "&c你没有权限使用此命令!");
- player.sendMessage(message);
- return true;
- }
-
- setBlocksForPlayer(player, null);
return true;
}
-
- private void setBlocksForPlayer(Player target, Player executor) {
- UUID targetId = target.getUniqueId();
- MessageManager msgManager = plugin.getMessageManager();
-
- if (plugin.getBlockManager().hasLifeBlocks(targetId)) {
- if (executor != null && !targetId.equals(executor.getUniqueId())) {
- String message = msgManager.getMessage("game.errors.player_already_has_blocks",
- "&c玩家 {player} 已经有生命方块了!");
- message = message.replace("{player}", target.getName());
- executor.sendMessage(message);
- } else {
- String alreadyHas = msgManager.getCommandMessage("setlifeblocks", "already_has",
- "&c你已经有生命方块了!使用 /checklifeblocks 查看位置");
- target.sendMessage(alreadyHas);
- }
- return;
- }
-
- if (!plugin.getSkinManager().isSkinLoaded(targetId)) {
- if (executor != null && !targetId.equals(executor.getUniqueId())) {
- String message = msgManager.getMessage("game.skin_loading",
- "&e玩家 {player} 的皮肤正在加载中,请稍候...");
- message = message.replace("{player}", target.getName());
- executor.sendMessage(message);
- } else {
- target.sendMessage("§e你的皮肤正在加载中,请稍候...");
- target.sendMessage("§7(如果长时间未加载完成,请重新加入服务器)");
- }
-
- plugin.getSkinManager().loadPlayerSkinAsync(target);
-
- Bukkit.getScheduler().runTaskLater(plugin, () -> {
- boolean success = plugin.getBlockManager().setLifeBlocks(target, target.getLocation());
- if (success) {
- if (executor != null && !targetId.equals(executor.getUniqueId())) {
- String message = msgManager.getMessage("game.blocks_generated_for_other",
- "&a已为玩家 {player} 生成生命方块!");
- message = message.replace("{player}", target.getName());
- executor.sendMessage(message);
- } else {
- String successMsg = msgManager.getCommandMessage("setlifeblocks", "success",
- "&a已为你生成 {blocks} 个生命方块!");
- ConfigManager config = plugin.getConfigManager();
- successMsg = successMsg.replace("{blocks}", String.valueOf(config.getBlocksPerPlayer()));
- target.sendMessage(successMsg);
- }
- } else {
- if (executor != null && !targetId.equals(executor.getUniqueId())) {
- String message = msgManager.getMessage("game.errors.failed_to_generate_blocks",
- "&c为玩家 {player} 生成生命方块失败!");
- message = message.replace("{player}", target.getName());
- executor.sendMessage(message);
- } else {
- target.sendMessage("§c生成失败,请稍后再试或联系管理员");
- }
- }
- }, 40L);
-
- return;
- }
-
- boolean success = plugin.getBlockManager().setLifeBlocks(target, target.getLocation());
-
- if (success) {
- if (executor != null && !targetId.equals(executor.getUniqueId())) {
- String message = msgManager.getMessage("game.blocks_generated_for_other",
- "&a已为玩家 {player} 生成生命方块!");
- message = message.replace("{player}", target.getName());
- executor.sendMessage(message);
- } else {
- String successMsg = msgManager.getCommandMessage("setlifeblocks", "success",
- "&a已为你生成 {blocks} 个生命方块!");
- ConfigManager config = plugin.getConfigManager();
- successMsg = successMsg.replace("{blocks}", String.valueOf(config.getBlocksPerPlayer()));
- target.sendMessage(successMsg);
- }
- } else {
- if (executor != null && !targetId.equals(executor.getUniqueId())) {
- String message = msgManager.getMessage("game.errors.failed_to_generate_blocks",
- "&c为玩家 {player} 生成生命方块失败!");
- message = message.replace("{player}", target.getName());
- executor.sendMessage(message);
- } else {
- target.sendMessage("§c生成失败,请确保周围有足够空间");
- }
- }
- }
-
- private void showHelp(Player player) {
- String helpMessage = plugin.getMessageManager().getCommandMessage("setlifeblocks", "help",
- "&6=== PlayerBlockLife 帮助 ===\n" +
- "&e/setlifeblocks &7- 设置你的生命方块\n" +
- "&e/setlifeblocks reset &7- 重置生命方块位置\n" +
- "&e/setlifeblocks other <玩家> &7- 为其他玩家设置(管理员)\n" +
- "&e/setlifeblocks help &7- 显示此帮助");
-
- player.sendMessage(helpMessage);
- }
}
\ No newline at end of file
diff --git a/src/main/java/com/playerblocklife/SkinManager.java b/src/main/java/com/playerblocklife/SkinManager.java
index c3ebd91..93db8bc 100644
--- a/src/main/java/com/playerblocklife/SkinManager.java
+++ b/src/main/java/com/playerblocklife/SkinManager.java
@@ -52,7 +52,7 @@ import java.util.concurrent.ConcurrentHashMap;
* 皮肤缓存默认保留7天,过期后自动重新获取。
*
* @author xiaobai
- * @version 2.1.0
+ * @version 2.2.0
* @since 1.0.0
*/
public class SkinManager {
@@ -97,7 +97,7 @@ public class SkinManager {
plugin.logInfo("开始加载皮肤: " + player.getName());
String skinBase64 = null;
- String skinSource = plugin.getConfigManager().getSkinSource();
+ String skinSource = "skinsrestorer"; // 使用默认值,因为配置已移除
// 根据配置的皮肤来源优先级获取皮肤
if ("skinsrestorer".equalsIgnoreCase(skinSource)) {
@@ -110,7 +110,7 @@ public class SkinManager {
} else if ("player_profile".equalsIgnoreCase(skinSource)) {
// 优先尝试PlayerProfile
skinBase64 = getSkinFromPlayerProfile(player);
- if (skinBase64 == null && plugin.getConfigManager().useSkinsRestorer()) {
+ if (skinBase64 == null && true) {
plugin.logInfo("PlayerProfile获取失败,尝试SkinsRestorer: " + player.getName());
skinBase64 = getSkinFromSkinsRestorer(player);
}
@@ -122,7 +122,7 @@ public class SkinManager {
}
// 缓存不存在,尝试其他来源
skinBase64 = getSkinFromPlayerProfile(player);
- if (skinBase64 == null && plugin.getConfigManager().useSkinsRestorer()) {
+ if (skinBase64 == null && true) {
skinBase64 = getSkinFromSkinsRestorer(player);
}
}
diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml
index b2bde41..2d6a867 100644
--- a/src/main/resources/config.yml
+++ b/src/main/resources/config.yml
@@ -1,19 +1,6 @@
# PlayerBlockLife 配置文件
config-version: 1
-# 方块设置
-blocks:
- # 每个玩家的生命方块数量
- amount: 5
- # 方块生成范围(以玩家为中心的正方形边长的一半)
- spread: 5
- # 方块生成最小范围(方块之间最小距离,单位:格)
- min-distance: 10
- # 方块埋藏深度(0为地面,负数为地下)
- depth: -1
- # 方块材质类型
- material: player_head
-
# 游戏规则
game:
# 方块被挖光时是否死亡
@@ -39,26 +26,6 @@ game:
# 经验数量
exp_amount: 5
-# 皮肤系统
-skin:
- # 是否启用皮肤系统
- enabled: true
- # 皮肤来源 (player_profile, local_cache, skinsrestorer)
- # player_profile: 使用Bukkit的PlayerProfile API(需要在线验证)
- # local_cache: 使用本地缓存的皮肤数据
- # skinsrestorer: 使用SkinsRestorer插件的皮肤数据(推荐用于离线服务器)
- source: skinsrestorer
-
- # 是否使用SkinsRestorer插件的皮肤(如果服务器有此插件)
- # 设置为true时,插件会优先从SkinsRestorer获取皮肤数据
- # 这对于离线服务器特别有用,可以避免默认Steve皮肤的问题
- use-skinsrestorer: true
-
- # 缓存设置
- cache:
- # 缓存过期时间(天)
- expire_days: 7
-
# 数据存储
storage:
# 存储类型 (yaml, json, sqlite)
@@ -80,17 +47,6 @@ protection:
# 是否保护生命方块不被活塞推动
protect_from_pistons: true
-# 自动生成设置
-auto-generation:
- # 玩家加入时是否自动生成生命方块
- enabled: true
- # 生成位置要求:上方无方块覆盖的地表
- require_open_sky: true
- # 最大尝试次数(如果找不到合适位置)
- max_attempts: 50
- # 生成失败时的处理方式 (none, notify, teleport_to_spawn)
- on_failure: notify
-
# 命令启用配置
commands:
# setlifeblocks 命令
diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml
index 4d63579..42218a3 100644
--- a/src/main/resources/plugin.yml
+++ b/src/main/resources/plugin.yml
@@ -1,11 +1,11 @@
name: PlayerBlockLife
-version: 3.0.0-experimental-1.20.4
+version: 2.2.0-1.20.4
main: com.playerblocklife.PlayerBlockLife
api-version: 1.20
author: xiaobai
-description: 玩家生命方块系统 - 方块被挖光则死亡
+description: PBL生存游戏模式 - 独特的生存竞技游戏
website: https://github.com/yourname/PlayerBlockLife
-prefix: PBL
+prefix: PlayerBlockLife
commands:
setlifeblocks:
@@ -26,6 +26,15 @@ commands:
permission: playerblocklife.check
permission-message: "§c你没有权限使用此命令!"
+ pbl:
+ description: PBL 主要控制命令
+ usage: |
+ / start [时间(分钟)] - 开始游戏
+ / rstgm - 重置游戏
+ aliases: [pblgame, pblcontrol]
+ permission: playerblocklife.admin
+ permission-message: "§c你没有权限使用此命令!"
+
pblreload:
description: 重载插件配置
usage: /
@@ -55,7 +64,7 @@ commands:
permissions:
playerblocklife.*:
- description: 所有 PlayerBlockLife 权限
+ description: 所有 PBL 权限
children:
playerblocklife.set: true
playerblocklife.check: true