docker pull tomcat:9.0-jdk11-openjdk
# 所以想要看觀迎頁面可以自己手動把頁面放到 /usr/local/tomcat/webapps 下
使用 Linux Ubuntu 為例子。
#可在 Windows 上用 PowerShell 安裝 WSL (預設安裝 WSL2),參考 安裝 WSL | Microsoft Learn
wsl --install
# Add Docker's official GPG key:
sudo apt update
sudo apt install ca-certificates curl
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc
# Add the repository to Apt sources:
sudo tee /etc/apt/sources.list.d/docker.sources <<EOF
Types: deb
URIs: https://download.docker.com/linux/ubuntu
Suites: $(. /etc/os-release && echo "${UBUNTU_CODENAME:-$VERSION_CODENAME}")
Components: stable
Architectures: $(dpkg --print-architecture)
Signed-By: /etc/apt/keyrings/docker.asc
EOF
sudo apt update
在 Windows 中,使用 virtual box 安裝好 Ubuntu 64 server 後,
如果想方便的分享 Windows 的資料夾給 Virtual box 開啟的 VM 使用,
可以使用 VirtualBox 的 "共用資料夾" 功能。
下面示範步驟
--------------------------------------------------------------------------
安裝 VirtualBox Guest Additions :
在 Virutalbox 開啟的 VM 視窗上選擇 "裝置" --> "插入 Guest Additions CD 像"
建立資料夾 (資料夾可自訂) : mkdir -p /mnt/cdrom
掛載光碟機上去 : mount /dev/cdrom /mnt/cdrom
進入光碟機:cd /mnt/cdrom
安裝 VirtualBox Guest Additions: sh ./VBoxLinuxAdditions.run --nox11
--------------------------------------------------------------------------
接著先把 VM 關機,在 Virtualbox 上對此VM做共用資料夾的設定:"設定" --> "共用資料夾"
可以在 Windows 端中放檔案,或在 VM 中放檔案,應該都可在各自端的地方看到共用檔案的變化了。
參考:
package com.my.mode.item;
import net.minecraft.entity.LivingEntity;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.item.IItemTier;
import net.minecraft.item.Item;
import net.minecraft.item.ItemGroup;
import net.minecraft.item.Rarity;
import net.minecraft.item.SwordItem;
import net.minecraft.item.crafting.Ingredient;
import net.minecraft.util.DamageSource;
import net.minecraft.world.World;
import net.minecraftforge.event.entity.living.LivingAttackEvent;
import net.minecraftforge.event.entity.living.LivingHurtEvent;
import net.minecraftforge.eventbus.api.SubscribeEvent;
public class SuperSword extends SwordItem{
public final static String ITEM_ID = "super_sword";
public SuperSword() {
super(new IItemTier() {
@Override
public int getMaxUses() {
// TODO Auto-generated method stub
return 100;
}
@Override
public float getEfficiency() {
// TODO Auto-generated method stub
return 0;
}
@Override
public float getAttackDamage() {
// TODO Auto-generated method stub
return 100;
}
@Override
public int getHarvestLevel() {
// TODO Auto-generated method stub
return 0;
}
@Override
public int getEnchantability() {
// TODO Auto-generated method stub
return 0;
}
@Override
public Ingredient getRepairMaterial() {
// TODO Auto-generated method stub
return null;
}
}, 100, 1, (new Item.Properties()).group(ItemGroup.COMBAT));
}
@SubscribeEvent
public void onLivingAttackEvent(LivingAttackEvent event) {
LivingEntity livingEntity = event.getEntityLiving();
World world = livingEntity.getEntityWorld();
if(!world.isRemote && livingEntity instanceof PlayerEntity) {
PlayerEntity playerEntity = (PlayerEntity) livingEntity;
if (playerEntity.getHeldItemMainhand().getItem() instanceof SuperSword
|| playerEntity.getHeldItemOffhand().getItem() instanceof SuperSword) {
//把事件取消,效果為攻擊無效,不會愛傷
event.setCanceled(true);
//增加效果,效果為對攻擊玩家 (player) 的生物造成傷害
if (event.getSource().getTrueSource() instanceof LivingEntity) {
LivingEntity attacker = (LivingEntity) event.getSource().getTrueSource();
attacker.attackEntityFrom(DamageSource.GENERIC, attacker.getMaxHealth() / 2);
}
}
}
}
@SubscribeEvent
public void onLivingAttackEvent(LivingHurtEvent event) {
LivingEntity livingEntity = event.getEntityLiving();
World world = livingEntity.getEntityWorld();
if(!world.isRemote && livingEntity instanceof PlayerEntity) {
PlayerEntity playerEntity = (PlayerEntity) livingEntity;
if (playerEntity.getHeldItemMainhand().getItem() instanceof SuperSword
|| playerEntity.getHeldItemOffhand().getItem() instanceof SuperSword) {
//把事件取消,效果為傷害無效,不會愛傷
event.setCanceled(true);
}
}
}
}
net.minecraft.item.SwordItem.SwordItem(IItemTier tier, int attackDamageIn, float attackSpeedIn, Properties builderIn)
LivingEntity livingEntity = event.getEntityLiving();
if (playerEntity.getHeldItemMainhand().getItem() instanceof SuperSword || playerEntity.getHeldItemOffhand().getItem() instanceof SuperSword)
if (event.getSource().getTrueSource() instanceof LivingEntity) {
LivingEntity attacker = (LivingEntity) event.getSource().getTrueSource();
attacker.attackEntityFrom(DamageSource.GENERIC, attacker.getMaxHealth() / 2);
}
package com.my.mode.item;
import com.my.mode.MyMod;
import net.minecraft.item.Item;
import net.minecraftforge.fml.RegistryObject;
import net.minecraftforge.registries.DeferredRegister;
import net.minecraftforge.registries.ForgeRegistries;
public class Items {
private static DeferredRegister<Item> REGISTER = null;
public static DeferredRegister<Item> getRegister(){
if (REGISTER == null) {
REGISTER = DeferredRegister.create(ForgeRegistries.ITEMS, MyMod.MOD_ID);
}
//注冊自製物品
REGISTER.register(SuperSword.ITEM_ID, () -> new SuperSword());
return REGISTER;
}
}
package com.my.mode;
import com.my.mode.item.Items;
import net.minecraftforge.eventbus.api.IEventBus;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.event.lifecycle.FMLCommonSetupEvent;
import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext;
public class ModEventBusHandler {
public static void register() {
IEventBus modEventBus = FMLJavaModLoadingContext.get().getModEventBus();
modEventBus.register(ModEventBusHandler.class);
modEventBus.register(new ModEventBusHandler());
//註冊自製物品,並將其註冊到 ModEventBus 中
Items.getRegister().register(modEventBus);
}
@SubscribeEvent
public void onCommonSetupEvent(FMLCommonSetupEvent event) {
ForgeEventBusHandler.register();
}
}
Items.getRegister().register(modEventBus);
package com.my.mode;
import com.my.mode.item.SuperSword;
import net.minecraft.client.Minecraft;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityType;
import net.minecraft.entity.LivingEntity;
import net.minecraft.entity.item.ItemEntity;
import net.minecraft.entity.monster.MonsterEntity;
import net.minecraft.entity.passive.FoxEntity;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.item.Item;
import net.minecraft.util.DamageSource;
import net.minecraft.world.World;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.event.entity.item.ItemTossEvent;
import net.minecraftforge.event.entity.living.LivingAttackEvent;
import net.minecraftforge.event.entity.living.LivingEvent;
import net.minecraftforge.event.entity.living.LivingHurtEvent;
import net.minecraftforge.eventbus.api.IEventBus;
import net.minecraftforge.eventbus.api.SubscribeEvent;
public class ForgeEventBusHandler {
public static void register() {
IEventBus forgeEventBus = MinecraftForge.EVENT_BUS;
forgeEventBus.register(ForgeEventBusHandler.class);
forgeEventBus.register(new ForgeEventBusHandler());
//註冊超級劍 (super sword) 的事件效果
forgeEventBus.register(new SuperSword());
forgeEventBus.register(SuperSword.class);
}
// Event 有分成在 ModEventUs 上的,或在 ForgeEventBus 上的,
// 此例的 ItemTossEvent 為在 ForgeEventBus 上的 Event
@SubscribeEvent
public void onItemTossEvent(ItemTossEvent event) {
//ItemTossEvent: 物品丟棄時觸發的 Event,
//在遊戲中可用按鍵 q 來丟棄物品
ItemEntity item = event.getEntityItem();
World world = item.getEntityWorld();
if (!world.isRemote) { // 判斷是否為 logical server 端,即處理邏輯的那端
// 如果此例的 isRemote 為 true,即為 logical client 端
// 可參考
// https://hackmd.io/@immortalmice/Hkj9s-CvU/https%3A%2F%2Fhackmd.io%2F%40immortalmice%2FrJKayrf9U
//建立一個狐狸物件
FoxEntity fox = new FoxEntity(EntityType.FOX, world);
fox.setPosition(item.getPosX(), item.getPosY(), item.getPosZ());
//將物件放到世界中
world.addEntity(fox);
}
}
}
forgeEventBus.register(new SuperSword()); forgeEventBus.register(SuperSword.class);
Note:
forgeEventBus.register(new SuperSword()); 用來註冊 SuperSword 裡的 non-static method,{
"item.mymod.super_sword": "Super Sword"
}{
"item.mymod.super_sword": "超級劍"
}{
"parent": "item/generated",
"textures": {
"layer0": "mymod:item/super_sword"
},
"display": {
"thirdperson_righthand": {
"rotation": [0, 90, 0],
"translation" : [0, 3, 0],
"scale" : [1, 1, 1]
}
}
}Note:
此時如果把 SuperSword 拿在左手,會發現一樣如拿在右手上一樣有吃到調整的設定,{
"type": "minecraft:crafting_shaped",
"pattern":
[
" x ",
"xxx",
" x "
],
"key":
{
"x":
{
"item": "minecraft:dirt"
}
},
"result":
{
"item": "mymod:super_sword",
"count": 1
}
}AngularJs 原生的 select option 的 required 驗證,
只能對 ng-model 的值為 undefined , "", null , NaN來做 required error。
參考:
ngModel.NgModelController 的 $isEmpty
但如果 ng-model 不是 undefined , "", null , NaN ,且其值不在 select option 中,
此時希望能檢查出驗證錯誤的話 (validation) ,
就需要自己實作。
例如可能的情境為:
如果 option list 裡都是不為 0 的數字,當 ng-model 是數字 0 時 (可能是程式常態 assign 賦值的),此時 required (或 ng-required) 就無法驗證出 required 的 validation error。
以下為我寫出來的簡單實作,
建立了一個名為 selectOptionCheckValidator 的 directive 來進行驗證:
html :
<div ng-app="app" ng-controller="controller as ctrl">
<form name="form">
<label><input type="radio" name="optionType" ng-value="1" ng-model="ctrl.optionType" /> Set option list 1</label>
<label><input type="radio" name="optionType" ng-value="2" ng-model="ctrl.optionType" /> Set option list 2</label>
<select name="mySelector" ng-model="ctrl.selectedVal" ng-options="selectOption.optionValue.id as selectOption.title for selectOption in ctrl.selectOptionList" select-option-check-validator ng-required="true">
</select>
<button ng-click="ctrl.selectedVal = 0">
Set selectedVal to 0
</button>
<div>
ctrl.selectedVal : {{ctrl.selectedVal}}
</div>
<div>
form.mySelector.$error.selectedValueInOptionList : {{form.mySelector.$error.selectedValueInOptionList ? "invalid" : "valid"}}
</div>
<div>
form.mySelector.$error.selectedValueInOptionList : {{form.mySelector.$error.required ? "invalid" : "valid"}}
</div>
</form>
</div>
angular.module("app", [])
.controller("controller", ["$scope", function($scope) {
var self = this;
self.optionType = 1;
self.selectedVal = 0;
self.optionList1 = [{
optionValue: {
id: 11
},
title: "id 11"
}, {
optionValue: {
id: 12
},
title: "id 12"
}];
self.optionList2 = [{
optionValue: {
id: 21
},
title: "id 21"
}, {
optionValue: {
id: 22
},
title: "id 22"
}];
self.selectOptionList = [];
$scope.$watch(angular.bind(this, function() {
return this.optionType;
}), function(newVal, oldVal) {
//if value of selectedValue can not be found in self.selectOptionList,
// selectedValue will be assigned to null.
if (newVal == 1) {
self.selectOptionList = self.optionList1;
} else if (newVal == 2) {
self.selectOptionList = self.optionList2;
self.selectedVal = 21;
}
});
}]).directive('selectOptionCheckValidator', ["$parse", function($parse) {
return {
require: 'ngModel',
link: function(scope, element, attrs, ngModel) {
var regex = /(.+)\sas\s(.+)\sfor\s(.+)\sin\s(.+)/;
var matchString = regex.exec(attrs.ngOptions);
var collectionExpression = matchString[4]; // "ctrl.selectOptionList"
var arryItemVarableName = matchString[3]; // "selectOption"
var valueExpression = matchString[1]; // "selectOption.optionValue.id"
scope.$watch(collectionExpression, function(newValue, oldValue) {
checkIsSelectedValueInOptionList();
});
element.on("change", function() {
scope.$apply(function() {
checkIsSelectedValueInOptionList();
});
});
function checkIsSelectedValueInOptionList() {
var isSelectedValueInOptionList = false;
var selectedValue = $parse(attrs.ngModel)(scope);
var optionList = $parse(collectionExpression)(scope);
if (optionList) {
isSelectedValueInOptionList = optionList.some(function(option) {
// create a custom scope object to render optionValue from valueExpression
var optionTempObject = new function(){
this[arryItemVarableName] = option;
};
var optionValue = $parse(valueExpression)(optionTempObject);
return optionValue == selectedValue;
});
ngModel.$setValidity("selectedValueInOptionList", isSelectedValueInOptionList);
}
}
}
};
}]);
Note
此例中用來解析 ng-option 字串的正規表達式較為簡易,
如果想知道完整的正規表達式,也就是可以解析 AngularJs ng-option 所有可能字串的正規達式,
可以參考官方源碼,其中可以看到正規表達式為:
^\s*([\s\S]+?)(?:\s+as\s+([\s\S]+?))?(?:\s+group\s+by\s+([\s\S]+?))?(?:\s+disable\s+when\s+([\s\S]+?))?\s+for\s+(?:([$\w][$\w]*)|(?:\(\s*([$\w][$\w]*)\s*,\s*([$\w][$\w]*)\s*\)))\s+in\s+([\s\S]+?)(?:\s+track\s+by\s+([\s\S]+?))?$
圖例:
實作了能偵測 textarea 內容並自動調整高度的 angularjs directive。
當 <textarea> 裡的文字變多需要高多行的 textarea 時,即需要更高的 textrea 時,
可以自動的調高高度,
而當文字變少時也能自動減少高度。
Note:
因為有使用到一些 jQuery 的 function,例如 innerHeight,所有需要先 include jQuery
直接上程式碼及範例:
html:<script src="https://code.jquery.com/jquery-3.5.1.min.js"></script> <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.8.2/angular.min.js"></script> This <textarea> will change its height to contain content. <div ng-app="app" ng-controller="controller as ctrl"> <textarea textarea-auto-resizer></textarea> </div>
angular.module("app", [])
.controller("controller", ["$scope", function($scope) {
var self = this;
self.text = "long words - long words - long words - long words - long words - long words - long words - long words - long words - long words - long words - long words - long words - long words - long words"
}])
.directive('textareaAutoResizer', ["$sce", function($sce) {
return {
link: function(scope, element, attrs, ngModel) {
autoResizeTextArea();
scope.$watch(attrs.ngModel, function(newValue, oldValue) {
autoResizeTextArea();
});
element.on("input", autoResizeTextArea);
function autoResizeTextArea(event) {
var dom = angular.element(element);
dom.css({
height: ""
});
if (dom.prop("scrollHeight") > dom.innerHeight()) {
//set dom height(without padding part) to scrollHeight - padding part
dom.height(dom.prop("scrollHeight") - (dom.innerHeight() - dom.height()));
}
}
}
};
}]);
參考: