type: speech

sound: 「クソだ、本当にクソだ、くだらねぇ・・・」

sound: (唾を吐く音)

sound: 「酒だ、酒が足りねぇ・・・」

sound: 「おい、誰かタバコ持ってねえか？」

sound: 「この1杯だけが、今日という1日を繋いでくれる・・・」

sound: 「この1本だけが、今日という1日を繋いでくれる・・・」

sound: 「どっかに人間はいないのか？ゾンビ共を撃ち殺したって何も面白くねぇ。」

sound: 「この前のヤマは大当たりだったぜ。」

sound: 「この前のヤマはマジでヤバかったぜ。」

sound: 「ああぁ、最ッ高にキクぜえぇぇ・・・」

sound: (狂気に満ちた笑い声)

sound: 「クソだ、本当にクソだ、くだらねぇ・・・」

sound: 「酒だ、酒が足りねぇ・・・」

sound: 「おい、誰かタバコ持ってねえか？」

sound: 「この1杯だけが、今日という1日を繋いでくれる・・・」

sound: 「この1本だけが、今日という1日を繋いでくれる・・・」

sound: 「どっかに人間はいないのか？ゾンビ共を撃ち殺したって何も面白くねぇ。」

sound: 「この前のヤマは大当たりだったぜ。」

sound: 「この前のヤマはマジでヤバかったぜ。」

sound: 「ああぁ、最ッ高にキクぜえぇぇ・・・」


魔法陣

type: trap

symbol: O

魔法陣（マゼンタ）

魔法陣（召喚）

color: magenta

魔法陣（赤）

魔法陣（火炎）

color: red

魔法陣（緑）

trapfunc.cppに用意されてるけど使われてない（と思われる）処理。なんか面白そうなので、本体側で完全に没になるまでは使ってみる。

action: drain

repo_name: Pedy/hentaimod

license: unknown


code:
--[[アクティビティ系]]--

--[[*気持ちいいこと*アクティビティ]]--
SEX = {
	sex_fun_bonus,		--行為による意欲ボーナス値
	sex_partner,		--行為の相手。いなければnil。
	pseudo_device,		--行為に使う道具。なければnil。
	is_love_sex			--愛のある行為かどうか
}

SEX.init = function(fun_bonus, ptnr, dvc, love_sex)
	DEBUG.add_msg("SEX.init--")

	--DEBUG.add_msg("fun_bonus:"..fun_bonus)
	--DEBUG.add_msg("ptnr:"..ptnr:disp_name())
	--DEBUG.add_msg("dvc:"..dvc:display_name())

	SEX.sex_fun_bonus = fun_bonus
	SEX.sex_partner = ptnr
	SEX.pseudo_device = dvc
	if not(love_sex == nil) then
		SEX.is_love_sex = love_sex
	else
		SEX.is_love_sex = false
	end
end

--[[*気持ちいいこと*アクティビティ中処理]]--
SEX.act_sex_do_turn = function(act, p)
	local turn = game.get_calendar_turn():get_turn()

	--SEX_BASE_TURNターンに1度意欲を上昇させ、メッセージを流す
	if (turn % SEX_BASE_TURN == 0) then
		--DEBUG.add_msg("sex_fun_bonus:"..SEX.sex_fun_bonus)
		--DEBUG.add_msg("SEX_FUN_DURATION:"..SEX_FUN_DURATION)
		--DEBUG.add_msg("SEX_FUN_DECAY_START:"..SEX_FUN_DECAY_START)
		game.add_msg(MOVINGDOING_TEXTS[math.random(#MOVINGDOING_TEXTS)])
		player:add_morale(SEX_MORALE_TYPE, SEX.sex_fun_bonus, 0, game.get_time_duration(SEX_FUN_DURATION), game.get_time_duration(SEX_FUN_DECAY_START))

		player:add_effect(efftype_id("movingdoing"), game.get_time_duration(SEX_BASE_TURN))
		if not(SEX.sex_partner == nil) then
			SEX.sex_partner(efftype_id("movingdoing"), game.get_time_duration(SEX_BASE_TURN))
		end
	end
end

--[[*気持ちいいこと*アクティビティ終了処理]]--
SEX.act_sex_finish = function(act, p)
	--activityにして途中でキャンセルできるようになったのはいいが、そのせいで変数管理がちょー面倒だぜ！
	--TODO:あまりにもスパゲティすぎて自分でも見返すと意味がわからない。整理する

	game.add_msg("*気持ちいいこと*を終えました。")
	act:set_to_null()

	player:remove_effect(efftype_id("lust"))
	player:remove_effect(efftype_id("movingdoing"))

	DEBUG.add_msg("Reset lust")

	--ここはDEBUG用
	if (SEX.sex_partner == nil) then
		DEBUG.add_msg("SEX.sex_partner is nil.")
	else
		DEBUG.add_msg("SEX.sex_partner:"..SEX.sex_partner:disp_name())
	end
	if (SEX.pseudo_device == nil) then
		DEBUG.add_msg("SEX.pseudo_device is nil.")
	else
		--DEBUG.add_msg("type(SEX.pseudo_device):"..type(SEX.pseudo_device))
		if (SEX.pseudo_device:is_null()) then
			DEBUG.add_msg("ERROR:SEX.pseudo_device *IS* null.")
		else
			DEBUG.add_msg("SEX.pseudo_device:"..SEX.pseudo_device:display_name())
		end
	end

	--行為の*完了時点で*プレイヤーのインベントリに存在する実際のdeviceのitemオブジェクトをここで取得する。
	local device
	if not(SEX.pseudo_device == nil) then
		device = get_item_by_id(player, SEX.pseudo_device:typeId())
		DEBUG.add_msg("device:"..device:display_name())
	end

	if not(SEX.sex_partner == nil) then
		--DEBUG.add_msg("SEX.sex_partner:"..SEX.sex_partner:disp_name())
		SEX.sex_partner:remove_effect(efftype_id("lust"))
		SEX.sex_partner:remove_effect(efftype_id("movingdoing"))

		--パートナーがいれば印象度を変動させる。
		local opinion = SEX.sex_partner.op_of_u
		DEBUG.add_msg("opinion:"..opinion.value)

		--変動値は超適当
		if (SEX.is_love_sex) then
			DEBUG.add_msg("is love sex.")
			opinion.trust = opinion.trust + 2
			opinion.value = opinion.value + 2
			opinion.fear = opinion.fear - 1
			opinion.anger = opinion.anger - 1
		else
			DEBUG.add_msg("is NOT love sex.")
			opinion.fear = opinion.fear + 5
			opinion.anger = opinion.anger + 1
			opinion.trust = opinion.trust - 2
			opinion.owed = opinion.owed - 1
		end
		SEX.sex_partner.op_of_u = opinion
	end

	DEBUG.add_msg("device check ->")
	--危ないやつを使ってた場合は確率で壊れる。
	if not(device == nil) then
		DEBUG.add_msg("device:typeId():"..device:typeId())
		if (device:typeId() == "condom_danger") then
			DEBUG.add_msg("wooga booga, it's danger! break check!")
			if(math.random(100) <= D_GOM_BREAK_CHANCE) then
				add_msg("避妊具が破れてしまいました！", H_COLOR.RED)
				player:i_rem(device)
				device = nil
			end
		end
	end

	DEBUG.add_msg("production check ->")
	--生産物の処理
	local liquid_of_u = item("h_semen", 1)
	liquid_of_u:set_relative_rot(0)		--そのままだとbirthdayがゲーム開始時のままなので、ここで新鮮にする

	if not(device == nil) then
		DEBUG.add_msg("device is "..device:display_name())

		if (player:has_item(device)) then
			DEBUG.add_msg("player have device.")

			--使用した道具を取り除き、(使用済み)のアイテムをプレイヤーの所持品に追加する。
			player:i_rem(device)
			local container = item("used_condom", 1)

			--(使用済み)のアイテムに液体を追加する。
			container:fill_with(liquid_of_u)
			player:i_add(container)
		end
	else

		map:add_item(player:pos(), liquid_of_u)

		if not(SEX.sex_partner == nil) then
			--プレイヤーとパートナー、両方に妊娠チェック
			SEX.check_preg(player, SEX.sex_partner)
			SEX.check_preg(SEX.sex_partner, player)
		end
	end

	DEBUG.add_msg("--end act_sex_finish()--")

end

--[[対象の妊娠判定を行う。]]--
SEX.check_preg = function(mother, father)
	DEBUG.add_msg("--check_preg--")

	--motherが母親、fatherが父親の想定。それ以外なら処理を抜ける
	if (mother == nil or father == nil) then
		return
	end

	DEBUG.add_msg("mother:"..mother:disp_name())
	DEBUG.add_msg("father:"..father:disp_name())

	if (mother.male) then
		return
	end
	if not(father.male) then
		return
	end

	mother:set_value(CREAMPIE_SEED_TYPE, "HUMAN")

	--判定に成功すれば孕む。失敗しても5日くらい溜まったままにする。
	if (preg_roll(mother)) then
		DEBUG.add_msg("impregnated.")
		mother:add_effect(efftype_id("impregnated"), game.get_time_duration(1), "num_bp", true)
	else
		DEBUG.add_msg("not impregnate.")
		mother:add_effect(efftype_id("creampie"), game.get_time_duration(72000))
	end

end


code:
--[[ペットのNPC化に関する処理系統]]--

ANTHRO = {
	--[[定数系]]--

	--[[ペットのNPC化時に表示する性別選択リスト]]--
	ANTHRO_PET_SEX_SUFFIX_LIST = {
		TITLE = "ところで、このペットの性別は？",
		LIST_ITEM = {
			{"オスです。", "_male"},
			{"メスです。", "_female"}
		}
	},

	--[[ペットのNPC化時に表示する変化系統]]--
	ANTHRO_PET_PATTERN = {
		--[[犬系]]--
		CANINE = {
			--[[オスメスの性別選択を行うかどうか]]--
			HAS_SEX_SUFFIX = true,
			--[[容姿の選択リストのタイトル]]--
			TITLE = "外見の好みは？",
			--[[容姿の選択リスト]]--
			PERSONAL_LIST = {
				{"ほぼ人間", "anthro_canine_morehuman"},
				{"半々程度", "anthro_canine_half"},
				{"ほぼ動物", "anthro_canine_lesshuman"}
			}
		},
		--[[猫系]]--
		FELINE = {
			HAS_SEX_SUFFIX = true,
			TITLE = "外見の好みは？",
			PERSONAL_LIST = {
				{"ほぼ人間", "anthro_feline_morehuman"},
				{"半々程度", "anthro_feline_half"},
				{"ほぼ動物", "anthro_feline_lesshuman"}
			}
		},
		--[[熊系]]--
		URSINE = {
			HAS_SEX_SUFFIX = true,
			TITLE = "外見の好みは？",
			PERSONAL_LIST = {
				{"ほぼ人間", "anthro_ursine_morehuman"},
				{"半々程度", "anthro_ursine_half"},
				{"ほぼ動物", "anthro_ursine_lesshuman"}
			}
		},
		--[[サキュバス系]]--
		SUCCUBI = {
			HAS_SEX_SUFFIX = false,
			TITLE = "外見の好みは？",
			PERSONAL_LIST = {
				{"ほぼ人間", "anthro_succubi_morehuman"},
				{"わりと悪魔", "anthro_succubi_lesshuman"}
			}
		},
		--[[インキュバス系]]--
		INCUBI = {
			HAS_SEX_SUFFIX = false,
			TITLE = "外見の好みは？",
			PERSONAL_LIST = {
				{"ほぼ人間", "anthro_incubi_morehuman"},
				{"わりと悪魔", "anthro_incubi_lesshuman"}
			}
		}
	}

}

--[[メイン処理]]--
ANTHRO.main = function(monster, selected_point)
	--TODO:リストメニューの選択がちょっとやっつけになっちゃってるのでそのうち見直す

	--NPC化の変化系統を取得する。
	local anthro_pet_pattern = ANTHRO.get_anthro_pet_pattern(monster)

	--リストが無ければ（変化先がなければ）キャンセル。
	if (anthro_pet_pattern == nil) then
		game.add_msg("この生物をNPC化する事はできません。")
		return
	end

	if not(game.query_yn("一度NPC化すると、二度と元の姿には戻せません。本当によろしいですか？")) then
		return
	end

	local npc_temp_id = ANTHRO.get_npc_temp_id(anthro_pet_pattern)

	--monsterを削除し、NPCを追加する。
	--NOTE:monsterの所持品にアイテムを追加することはできても取得することができないので、ペットに預けていたアイテムがすべて消えてしまう。
	g:remove_zombie(monster)		--こんなFunction名だけどゾンビじゃなくても消せる。

	local npc_id = map:place_npc(selected_point.x, selected_point.y, npc_template_id(npc_temp_id))
	DEBUG.add_msg("npc_id:"..npc_id)

	--TODO:本当はこの場でNPCの名前を変更したりしたいんだけど無理なので名前の巻物を与えてお茶をにごす
	local scroll = item("scroll_of_naming", 1)
	map:add_item(player:pos(), scroll)
	add_msg("あなたの足元に何かが転がってきた。", H_COLOR.GREEN)

	return

end

--[[NPC化の変化系統を取得する]]--
ANTHRO.get_anthro_pet_pattern = function(monster)

	local anthro_pet_pattern = nil

	local mtype = monster.type
	DEBUG.add_msg("mtype:"..mtype:nname())

	--"DOGFOOD"フラグを持つmonsterであれば犬系変化リスト。	この条件だと犬以外で"DOGFOOD"フラグを持つmonster（別modの野良メイドとか）でも対象になってしまう。...まあそれはそれで良し
	if (mtype:has_flag("DOGFOOD")) then
		DEBUG.add_msg("-->ANTHRO_CANINE")
		anthro_pet_pattern = ANTHRO.ANTHRO_PET_PATTERN.CANINE

	--"CATFOOD"フラグを持つmonsterであれば猫系変化リスト。
	elseif (mtype:has_flag("CATFOOD")) then
		DEBUG.add_msg("-->ANTHRO_FELINE")
		anthro_pet_pattern = ANTHRO.ANTHRO_PET_PATTERN.FELINE

	--monsterのidが"mon_bear_cub"か"mon_bear"なら熊系変化リスト。
	elseif (mtype.id == mtype_id("mon_bear_cub") or mtype.id == mtype_id("mon_bear")) then
		DEBUG.add_msg("-->ANTHRO_URSINE")
		anthro_pet_pattern = ANTHRO.ANTHRO_PET_PATTERN.URSINE

	--monsterの種族が"CUBI"の場合
	elseif (mtype:in_species(species_id("CUBI"))) then
		DEBUG.add_msg("-->ANTHRO_CUBI")

		if (mtype:in_species(species_id("FEMALE"))) then
			--種族が"FEMALE"ならサキュバス系変化リスト。
			DEBUG.add_msg("  -->...is FEMALE.")
			anthro_pet_pattern = ANTHRO.ANTHRO_PET_PATTERN.SUCCUBI

		elseif (mtype:in_species(species_id("MALE"))) then
			--種族が"MALE"ならインキュバス系変化リスト。
			DEBUG.add_msg("  -->...is MALE.")
			anthro_pet_pattern = ANTHRO.ANTHRO_PET_PATTERN.INCUBI

		else
			--その他なら...その他ってなんだ？
			DEBUG.add_msg("  -->...is WHAT?")
		end

	end

	return anthro_pet_pattern
end

--[[NPC化の変化系統パターンから変化対象のNPCのtemplateIDを取得する]]--
ANTHRO.get_npc_temp_id = function(anthro_pet_pattern)

	local pet_sex_suffix
	--変化系統パターンにてペットの性別選択の指定がある場合は選択する
	if (anthro_pet_pattern.HAS_SEX_SUFFIX) then
		pet_sex_suffix = ANTHRO.get_sex_suffix()
	else
		pet_sex_suffix = ""
	end

	local anthro_menu_list = anthro_pet_pattern.PERSONAL_LIST

	--容姿の選択リストからnpc_template_idを取得する。
	local menu = game.create_uimenu()
	menu.title = anthro_pet_pattern.TITLE

	for key, value in pairs(anthro_menu_list) do
		menu:addentry(value[1])
	end

	menu:query(true)
	choice = menu.selected

	--さっき取得したペット性別接尾語をつけておく。
	--あまりいい方法じゃないな...しかしmap:place_npcはid単位でしか指定できないのでこうでもしないと選択式にできない。定義するnpc_temp_idもn*2に増えてしまう...しかし性別の選択方式だけは絶対に譲れないジレンマ。せめてNPC生成後にLUAで干渉できれば...
	local npc_temp_id = anthro_menu_list[choice+1][2]..pet_sex_suffix
	DEBUG.add_msg("npc_temp_id:"..npc_temp_id)

	return npc_temp_id
end

ANTHRO.get_sex_suffix = function()

	--ペットの性別を選択する。
	local menu_suffix = game.create_uimenu()
	local choice = -1
	menu_suffix.title = ANTHRO.ANTHRO_PET_SEX_SUFFIX_LIST.TITLE

	for key, value in pairs(ANTHRO.ANTHRO_PET_SEX_SUFFIX_LIST.LIST_ITEM) do
		menu_suffix:addentry(value[1])
	end

	menu_suffix:query(true)
	choice = menu_suffix.selected

	local pet_sex_suffix = ANTHRO.ANTHRO_PET_SEX_SUFFIX_LIST.LIST_ITEM[choice+1][2]
	DEBUG.add_msg("pet_sex_suffix:"..pet_sex_suffix)

	return pet_sex_suffix

end


code:
--[[定数]]--

--[[文字のハイライト色パターン]]--
H_COLOR = {
	BLACK		= "black",
	WHITE		= "white",
	LIGHT_GRAY	= "light_gray",
	DARK_GRAY	= "dark_gray",
	RED			= "red",
	GREEN		= "green",
	BLUE		= "blue",
	CYAN		= "cyan",
	MAGENTA		= "magenta",
	BROWN		= "brown",
	LIGHT_RED	= "light_red",
	LIGHT_GREEN	= "light_green",
	LIGHT_BLUE	= "light_blue",
	LIGHT_CYAN	= "light_cyan",
	PINK		= "pink",
	YELLOW		= "yellow"
}

--[[*気持ちいいこと*中に表示されるテキスト]]--
MOVINGDOING_TEXTS = {
	"*パコパコ*",
	"『はぁはぁ』",
	"『きくぅ』",
	"『るん♪』",
	"『うふふ♪』",
	"『ねうねう♪』"
}

--[[レイダー的な敵キャラ会話のテキスト]]--
VULGAR_SPEECH_TEXTS = {
	--ターゲットを発見した時
	TARGET_ACQUIRE = {
		--[[露骨なパロディネタはいちおう自粛。
			"「地獄だ、やあ！」",
		]]--
		"「バア！」",
		"「何だ！？」",
		"「おい、敵だ！」",
		"「ハッ！見つけたぞ！」",
		"「ヒャッハー！」",
		"「頭ぁねじ切ってオモチャにしてやるぜぇ！」",
		"「ヒャッハー！新鮮なオモチャだぁ！」",
		"「ヒャッハー！新鮮な肉だぁ！」",
		"「今日は人間の肉で宴だ！」",
		"「レイプ・タァァァイム！」",
		"(狂気に満ちた笑い声)",
		"「HELL YEAH!」",
		"「地獄だ、イェア！」"
	},
	--ターゲットと交戦中
	TARGET_ENGAGE = {
		--[[露骨なパロディネタはいちおう自粛。
			"「殺しは初めてじゃないんだよ、新米！」",
			"「何百回とやってきた！そうすれば変わるだろう！？」",
			"「自分を制して、悪に徹しろ！」",
		]]--
		"「ヒャッハー！」",
		"「怖いか、えぇ？」",
		"「来いよ、おら！」",
		"「血だ！血だ！血だぁ！」",
		"「血を見せろぉ！」",
		"「犯してから殺すかぁ？殺してから犯すかぁ？...どぉぉぉっちでもいいなぁぁ！」",
		"「犯して、殺して、また犯してやるよぉ！」",
		"「助けてやってもいいぜぇ？...お前で遊び終わったらなぁ！」",
		"(狂気に満ちた笑い声)",
		"(唸り声)",
		"「くたばれ！」",
		"「このクソ...」",
		"「まだ死なねぇのか！？」",
		"「なんで死なねぇんだ！？」",
		"「殺しは初めてじゃないんだよ、ルーキー！」",
		"「人間として生きられないなら、悪魔として生きるほかねぇんだ！」"
	},
	--ターゲットを見失った時
	TARGET_LOST = {
		"(舌打ちの音)",
		"「逃げ足の速いヤツだ...」",
		"「出てこいよぉ、すぐに殺してやるからさぁ！」",
		"「隠れてないで出てこい！」",
		"「気をつけろ、まだそのへんに隠れてやがるぞ...」",
		"「逃げんじゃねーよ！」",
		"「くそっ、どこ行きやがった？」"
	}
}

--[[profession"一人と一匹"スタート時のペット選択リストまとめ]]--
PROF_PET_LIST = {
	TITLE = "あなたのペットは...",
	LIST_ITEM = {
		{
			ENTRY = "イヌだ！",
			PET_ID = "mon_dog",
			BONUS_ITEM = {"pet_carrier", "dog_whistle"}
		},
		{
			ENTRY = "ネコだ！",
			PET_ID = "mon_cat",
			BONUS_ITEM = {"pet_carrier", "can_tuna"}
		},
		{
			ENTRY = "クマだ！",
			PET_ID = "mon_bear_cub",
			BONUS_ITEM = {}
		},
		{
			ENTRY = "サキュバスだ！",
			PET_ID = "mon_succubi",
			BONUS_ITEM = {"holy_choker"}
		}
	}
}

--[[アークCUBIのmonster_idリスト。主に上位敵の召喚用に使う。]]--
MON_ARCH_CUBI_LIST = {
	"mon_succubi_sadist",
	"mon_succubi_somnophilia",
	"mon_succubi_exhibitionism",
	"mon_succubi_lactophilia",
	"mon_incubi_sthenolagnia",
	"mon_incubi_phalloplas",
	"mon_incubi_hoplophilia"
}

SEX_MORALE_TYPE		= morale_type("morale_sex_good")	--*気持ちいいこと*の意欲タイプ
SEX_BASE_TURN		= 100		--行為1wait当たりに掛かる基準ターン数(1ターン = 約6秒)。100=10分。
SEX_MAX_TURN		= 1800		--行為全体に掛かる最大ターン数。1800=3時間。
SEX_FUN_DURATION	= 600		--行為による意欲がどの程度長続きするかのtime_duration。
SEX_FUN_DECAY_START	= 150		--行為による意欲が冷め始めるまでのtime_duration。

D_GOM_BREAK_CHANCE	= 50		--あぶない方の避妊具が使用時に破損する確率(%)。

PREG_CHANCE = 10				--基礎妊娠確立(%)。
DEFAULT_PREG_SPEED_RATIO = 1	--孕んだ子供の成長スピード比率。

DEFAULT_NPC_NAME = "ﾄﾑ"		--NPC新規生成時のデフォルト名。みんな大好きトム。

EFF_SPELL_CHARGE_INT_FACTOR = 30	--モンスター攻撃の魔法詠唱にかかるint_dur_factor。

--[[set_valueの値設定に使う名称]]--
EVENT_GOATHEAD_DEMON = "event_goathead_demon"				--モンスター"mon_goathead_demon"との会話イベントを発生させたかどうか
EVENT_DEMONBEING_SCHOOLGIRL = "event_demonbeing_schoolgirl"	--npc"demonbeing_schoolgirl"を発生させたかどうか
CREAMPIE_SEED_TYPE = "creampie_seed_type"					--注がれた種の種族を保持する。あなたがパパになるんですよ？


code:
--[[主にモンスターの特殊攻撃関連の処理を記述する。]]--

local body_part_texts = {
	"頬", "唇", "耳", "指", "手", "腕", "胸", "下腹部", "股間", "尻", "内股", "脚"
}

local action_texts = {
	"を撫でた。",
	"を撫でまわした。",
	"を軽くつまんだ。",
	"を弄んだ。",
	"にキスした。",
	"に舌を這わせた。",
	"を舐めまわした。",
	"をしゃぶった。",
	"に熱い吐息を吹きかけた。"
}

--[[デバッグ用ファンクション]]--
function matk_hello(monster)
--function matk_hello(monster, target)
	--NOTE:LUA拡張版DDAはtarget引数を持つmonster_attackが無いっぽいので、monster:attack_target()で攻撃対象を取得している
	local target = monster:attack_target()

	game.add_msg("...who?")

	if (target == nil) then
		game.add_msg("who!?")
	else
		game.add_msg("Ah, it's you, "..target:disp_name().."!")
		game.add_msg(monster:disp_name()..":Hello, "..target:disp_name().."!")
	end

end

--[[↓こっから共通処理↓]]--

--[[monsterが攻撃しようとしているターゲットが射程距離内にいるかどうか判定し、可能な場合はCreatureを返す。]]--
function get_attackable_target(monster, max_range)
	--攻撃しようとしているターゲットを取得
	local someone = monster:attack_target()

	if (someone == nil) then
		--DEBUG.add_msg("Um... never mind.")
		return
	end

	--DEBUG.add_msg("check distance ->")

	--ターゲットとの距離が最大射程よりも離れていたら何もしない
	local dist = game.distance(monster:posx(), monster:posy(), someone:posx(), someone:posy())		--line.cppを参照。
	if (dist > max_range) then
		--DEBUG.add_msg("Out of range.")
		return
	end
	--DEBUG.add_msg("OK!")

	--DEBUG.add_msg("Target is YOU, "..someone:disp_name().."!")

	return someone
end

function get_attackable_chara(monster, max_range)
	local target = get_attackable_target(monster, max_range)

	--PlayerかNPCだけを対象とする。
	if (target == nil or target:is_monster()) then
		--DEBUG.add_msg("not Character? Oh...")
		return
	end

	--NOTE:monster:attack_target()で取得できるのはCreatureクラスなのでCharacterクラスを取り直してやる
	--local ret = g:character_at(target:pos())
	if target:is_player() then
		return player
	end
	local ret = game.get_npc_at(target:pos())

	return ret
end

function get_attackable_player(monster, max_range)
	local target = get_attackable_target(monster, max_range)

	--PlayerかNPCだけを対象とする。
	if (target == nil or target:is_monster()) then
		--DEBUG.add_msg("not Character? Oh...")
		return
	end

	--NOTE:monster:attack_target()で取得できるのはCreatureクラスなのでPlayerクラスを取り直してやる
	--local ret = g:player_at(target:pos())
	if target:is_player() then
		return player
	end
	local ret = game.get_npc_at(target:pos())

	return ret
end

--[[四捨五入。実数numを、小数idp桁で丸める。ネットの拾い物]]
function math.round(num, idp)
	if (idp and idp > 0) then
		local mult = 10^idp
		return math.floor(num * mult + 0.5) / mult
	end
	return math.floor(num + 0.5)
end

--[[ハートマークの二次曲線（いわゆるLove Formura）のtripointを計算する。ネットの拾い物]]
--[[
	NOTE:CDDAの仕様として、マップの横軸xと縦軸yは
	0-->x	右へ行くほど増
	|
	V
	y		下へ行くほど増
	の関係になっているため、数学的な二次曲線グラフとはy軸を反転して考えてやる必要がある。
]]
function LoveFormula(center, radius, curvature)
	--DEBUG.add_msg("LoveFormula:start")

	local tripoint_list = {}
	--local center = player:pos()
	--DEBUG.add_msg("center:"..center.x.."/"..center.y)

	local y
	local b = curvature			--ハート曲線の曲がり具合。0だと完全な円になる。
	local dx = 1 / radius * 2	--xのループカウント刻み。刻みを小さくすると綺麗な曲線になるが処理が重くなる...
	local pos
	local rx
	local ry

	--ハートの曲線のtripointを計算
	-- 0 <= x <= 1, 0 <= y <= 1 の部分。つまり右上
	for x = 0, 1, dx do
		y = math.sqrt(1 - x * x) + b * math.sqrt(x)
		--DEBUG.add_msg("x="..x..", y="..y)

		--半径を考慮して丸めたpointを求める
		rx = math.round(x * radius, 0)
		ry = math.round(y * radius * -1, 0)

		pos = tripoint(rx + center.x, ry + center.y, 0)
		table.insert(tripoint_list, pos)

		--x反転箇所も確保しておく
		pos = tripoint(rx * -1 + center.x, ry + center.y, 0)
		table.insert(tripoint_list, pos)
	end

	-- 0 <= x <= 1, -1 <= y <= 0 の部分。つまり右下
	for x = 1, 0, dx * -1 do
		y = (math.sqrt(1 - x * x)) * -1 + b * math.sqrt(x)
		--DEBUG.add_msg("x="..x..", y="..y)

		--半径を考慮して丸めたpointを求める
		rx = math.round(x * radius, 0)
		ry = math.round(y * radius * -1, 0)

		pos = tripoint(rx + center.x, ry + center.y, 0)
		table.insert(tripoint_list, pos)

		--x反転箇所も確保しておく
		pos = tripoint(rx * -1 + center.x, ry + center.y, 0)
		table.insert(tripoint_list, pos)
	end

	--DEBUG.add_msg("LoveFormula:end")

	return tripoint_list
end

--[[targetがヤれる状態かどうか判定]]--
function can_wife(monster, target)
	local immobile_effect = {
		"webbed", "beartrap", "crushed", "grabbed", "in_pit", "sleep", "zapped"
	}

	if (target == nil) then
		return false
	end

	DEBUG.add_msg("wife target:"..target:disp_name())

	--ターゲットがぱんつはいてないかチェック
	if (target:wearing_something_on("bp_leg_l") and target:wearing_something_on("bp_leg_r")) then
		if (math.random(10) <= 2) then
			game.add_msg("<color_yellow>"..monster:disp_name().."はあなたの股間を狙っている...</color>")
		end
		return false
	end

	DEBUG.add_msg("no wearing pants!")

	--ターゲットが身動き取れない状態かどうかチェック
	--targetが移動できない状態の場合はヤれる。適当な仕様。
	for key, value in pairs(immobile_effect) do
		if (target:has_effect(efftype_id(value))) then
			DEBUG.add_msg("immobile!")
			return true
		end
	end

	DEBUG.add_msg("can't wife.")

	return false
end

--[[イく判定]]--
function has_cum(me)
	--TODO:超適当な判定。

	local limit = 100		--v1.3から仕様変更。"lust"のintensityがこれを超えたら達する

	--"lust"のintensityを取得
	local intensity = me:get_effect_int(efftype_id("lust"))

	DEBUG.add_msg("intensity:"..intensity)

	if (intensity >= limit) then
		--"lust"を取り除き、少しだけwaitを掛ける。
		game.add_msg("<color_green>"..me:disp_name().."は達した！</color>")
		me:remove_effect(efftype_id("lust"))
		me:mod_moves(-50)

		return true
	end

	return false
end

--[[対象の体液タイプ（アイテム）を取得する。]]--
function get_ejacuate_item(me)
	local liquid

	if (me:is_player()) then
		--対象がプレイヤーなら人間の体液。
		liquid = item("h_semen", 1)

	elseif (me:is_monster()) then
		--対象がモンスターなら魔性の体液。
		liquid = item("d_cum", 1)
	end

	if not(liquid == nil) then
		liquid:set_relative_rot(0)		--そのままだとbirthdayがゲーム開始時のままなので、ここで新鮮にする
	else
		DEBUG.add_msg("ERROR:ejacuate_item is nil.")
	end

	return liquid
end

--[[レイダー的な会話テキスト表示]]--
--TODO:メッセージ表示じゃなくて、"音"として会話させたい
function matk_vulgar_speech(monster)
	local max_range = 30		--特殊攻撃の最大射程

	--DEBUG.add_msg("vulgar_speech?")

	--攻撃しようとしているターゲットを取得
	local target = get_attackable_player(monster, max_range)

	if (target == nil) then
		--今回のチェックで捕捉対象を見逃した場合はテキストを表示。
		if (monster:has_effect(efftype_id("target_acquired"))) then
			local speech_texts = VULGAR_SPEECH_TEXTS.TARGET_LOST
			add_msg(speech_texts[math.random(#speech_texts)], H_COLOR.YELLOW)

			monster:remove_effect(efftype_id("target_acquired"))
		end

	else
		if (monster:has_effect(efftype_id("target_acquired"))) then
			--交戦中の場合。
			local speech_texts = VULGAR_SPEECH_TEXTS.TARGET_ENGAGE
			add_msg(speech_texts[math.random(#speech_texts)], H_COLOR.YELLOW)

		else
			--今回のチェックで初めて対象を捕捉した場合。
			local speech_texts = VULGAR_SPEECH_TEXTS.TARGET_ACQUIRE
			add_msg(speech_texts[math.random(#speech_texts)], H_COLOR.YELLOW)

			monster:add_effect(efftype_id("target_acquired"), game.get_time_duration(1), "num_bp", true)
		end
	end

	--DEBUG.add_msg("vulgar_speech!")
end

--[[状態異常"corrupt"をtargetに与える。]]--
function gain_corrupt(target, dur)

	--対象のINT値の確率で抵抗判定。判定に失敗したら"corrupt"を与える。
	if (math.random(20) > target.int_cur) then
		target:add_effect(efftype_id("corrupt"), game.get_time_duration(dur))
	else
		game.add_msg("しかし欲望に抗いました。")
	end

end

--[[↓こっからモンスター特殊攻撃処理↓]]--

--[[誘惑攻撃(近距離)]]--
function matk_seduce(monster)
	local max_range = 1		--特殊攻撃の最大射程

	--DEBUG.add_msg("seduce you?")

	--攻撃しようとしているターゲットを取得
	local target = get_attackable_chara(monster, max_range)

	if (target == nil) then
		return
	end

	--ここでモンスターに行動コストを追加。
	monster:mod_moves(-100)

	--==ターゲットの回避ロール==--
	--超回避システムが発動中なら必ず回避。
	if (target:uncanny_dodge()) then
		game.add_msg(monster:disp_name().."が手を伸ばしてきましたが、"..target:disp_name().."は物凄い勢いで回避しました！")
		return
	end

	--物理的な行動による回避ロール。player.dodge_roll()についてはmelee.cppとかを参照。
	if (math.random(100) <= target:dodge_roll()) then
		game.add_msg(monster:disp_name().."が手を伸ばしてきましたが、"..target:disp_name().."は回避しました！")
		return
	end

	--HENTAI的なテキストを取得。
	local bp_names = {table.unpack(body_part_texts)}

	--TODO:尾とか羽とか、特別な部位（特質）がある場合はここでbp_namesに追加しようと思ったけど力尽きた

	local bp_text = bp_names[math.random(#bp_names)]
	local act_text = action_texts[math.random(#action_texts)]

	--HENTAI的なテキストを表示。
	game.add_msg("<color_pink>"..monster:disp_name().."は"..target:disp_name().."の"..bp_text..act_text.."</color>")

	--状態異常"lust"と"corrupt"をtargetに与える。
	--target:add_effect(efftype_id("corrupt"), game.get_time_duration(100))
	gain_corrupt(target, 100)
	target:add_effect(efftype_id("lust"), game.get_time_duration(4))

	--DEBUG.add_msg("seduce you!")

end

--[[誘惑攻撃(遠距離)]]--
function matk_tkiss(monster)
	local max_range = 10		--特殊攻撃の最大射程

	--DEBUG.add_msg("kiss you?")

	--攻撃しようとしているターゲットを取得
	local target = get_attackable_chara(monster, max_range)

	if (target == nil) then
		return
	end

	--ここでモンスターに行動コストを追加。
	monster:mod_moves(-50)

	--==ターゲットの回避ロール==--
	--超回避システムが発動中なら必ず回避。
	if (target:uncanny_dodge()) then
		game.add_msg(monster:disp_name().."が投げキッスを放ちましたが、"..target:disp_name().."は物凄い勢いで回避しました！")
		return
	end

	--物理的な行動による回避ロール。...投げキッスって回避するとかそういう物じゃない気もするが
	if (math.random(100) <= target:dodge_roll()) then
		game.add_msg(monster:disp_name().."が投げキッスを放ちましたが、"..target:disp_name().."は回避しました！")
		return
	end

	game.add_msg("<color_pink>"..monster:disp_name().."は"..target:disp_name().."に投げキッスした！</color>")

	--状態異常"lust"と"corrupt"をtargetに与える。
	--target:add_effect(efftype_id("corrupt"), game.get_time_duration(50))
	gain_corrupt(target, 50)
	target:add_effect(efftype_id("lust"), game.get_time_duration(2))

	--DEBUG.add_msg("kiss you!")

end

--[[脱がす攻撃]]--
function matk_stripu(monster)
	local max_range = 1			--特殊攻撃の最大射程
	local max_value = 250		--脱がしたアイテムを置くか奪うかの基準体積(mL。jsonで指定されてるvolume=1(c)は250(mL)の扱いになる。)

	--DEBUG.add_msg("strip you?")

	--ヤってる最中は脱がさない。着衣プレイっていいよね...
	if (monster:has_effect(efftype_id("dominate"))) then
		return
	end

	--攻撃しようとしているターゲットを取得
	local target = get_attackable_chara(monster, max_range)

	if (target == nil) then
		return
	end

	--ここでモンスターに行動コストを追加。
	monster:mod_moves(-100)

	--==ターゲットの回避ロール==--
	--超回避システムが発動中なら必ず回避。
	if (target:uncanny_dodge()) then
		game.add_msg(monster:disp_name().."は"..target:disp_name().."の衣服を脱がそうとしてきましたが、物凄い勢いで回避しました！")
		return
	end

	--物理的な行動による回避ロール。
	if (math.random(100) <= target:dodge_roll()) then
		game.add_msg(monster:disp_name().."は"..target:disp_name().."の衣服を脱がそうとしてきましたが、回避しました！")
		return
	end

	--ターゲットが着用しているアイテムの1つをランダムに取得。
	--get_wears(target, "bp_torso")
	--get_wears(target)
	--local item = target:i_at(-2)
	local item = get_random_wear(target)

	if (item == nil) then
		--DEBUG.add_msg("Nothing? Noooo...")
		return
	end

	--DEBUG.add_msg(item:display_name())

	--脱がす！
	--NOTE:player:takeoff()だと確認プロンプトが出てしまうので、追加→削除の流れで脱がす。
	local vol = item:volume()
	--DEBUG.add_msg("volume:"..vol:value())

	if (vol:value() > max_value) then
		--itemの体積が大きい場合はmonsterの足元に落とす。
		game.add_msg("<color_pink>"..monster:disp_name().."は"..target:disp_name().."の</color>"..item:display_name().."<color_pink>をさっと脱がせました！</color>")
		map:add_item(monster:pos(), item)
	else
		--itemの体積が十分に小さい場合（たとえば下着）はmonsterの所持品に含める。
		game.add_msg("<color_pink>"..monster:disp_name().."は"..target:disp_name().."の</color>"..item:display_name().."<color_pink>を脱がせると、そのまま奪い取りました！</color>")
		monster:add_item(item)
	end
	if (target:has_item(item)) then
		target:i_rem(item)
	end

	--DEBUG.add_msg("strip you!")

	return
end

--[[Wife攻撃]]--
--TODO:functionを分ける。神クラスになりかけてる
function matk_wifeu(monster)
	local max_range = 1		--特殊攻撃の最大射程

	--DEBUG.add_msg("wife you?")

	--攻撃しようとしているターゲットを取得
	local target = get_attackable_player(monster, max_range)

	if (target == nil) then
		return
	end

	--ターゲットがヤれる状態かどうかチェック
	if (not can_wife(monster, target)) then
		--直前までヤってたならモンスターから"dominate"を外し、対象からも"gotwifed"のintensityを1つ下げる。
		--FIXME:PRETTY BUGGY. プレイヤーとNPCを二人同時に相手してたり、ヤってる途中に死んだりすると対象に"gotwifed"が残ったままになってしまう。
		if (monster:has_effect(efftype_id("dominate"))) then
			monster:remove_effect(efftype_id("dominate"))
			target:add_effect(efftype_id("gotwifed"), game.get_time_duration(-1), "num_bp", true)
		end

		return
	end

	--ヤる！
	if (monster:has_effect(efftype_id("dominate"))) then
		game.add_msg("<color_pink>"..monster:disp_name().."は腰を振り続けている...</color>")

	else
		local intensity = target:get_effect_int(efftype_id("gotwifed"))
		DEBUG.add_msg("intensity: "..intensity)

		if (intensity >= 3) then
			--ターゲットが既にお取り込み中の場合は...自主トレを行う。
			game.add_msg("<color_pink>"..monster:disp_name().."は"..target:disp_name().."を見つめながら自分の体をまさぐっている...</color>")

			monster:add_effect(efftype_id("lust"), game.get_time_duration(6))
			monster:mod_moves(-100)

			return

		else
			--スペースがあれば突っ込む。何をとは言わんが。
			game.add_msg("<color_pink>"..monster:disp_name().."は"..target:disp_name().."を押さえつけると、ゆっくりと体を沈めていきました...</color>")

			--モンスターに"dominate"を、対象に"gotwifed"を与える。
			monster:add_effect(efftype_id("dominate"), game.get_time_duration(1), "num_bp", true)
			target:add_effect(efftype_id("gotwifed"), game.get_time_duration(1), "num_bp", true)

			lost_virgin(target, false)
		end
	end

	--状態異常"corrupt"をtargetに与える。
	--target:add_effect(efftype_id("corrupt"), game.get_time_duration(200))
	gain_corrupt(target, 200)

	--状態異常"lust"をtargetとmonster両方に与える。
	target:add_effect(efftype_id("lust"), game.get_time_duration(8))

	--monsterの方は相手の器用の分だけプラス。
	--こう何というか、先にイった方が負けみたいなバトルを繰り広げたいけどバランスをどうすればいいんだ
	monster:add_effect(efftype_id("lust"), game.get_time_duration(8 + target.dex_cur))

	--イく！
	log.message("532")
	if (has_cum(target)) then
		local liquid = get_ejacuate_item(target)
		map:add_item(target:pos(), liquid)
	end
	log.message("537")
	if (has_cum(monster)) then
		local liquid = get_ejacuate_item(monster)
		map:add_item(monster:pos(), liquid)

	log.message("542")
		if (is_cubi(monster)) then
			--1/5の確率で相手に"FIEND"タイプの変異を与える。どこに注がれたかはこの際考慮しない。血清注射でも変異するんだからどこの穴でも[自主規制]
			--if (1 >= math.random(5)) then
	log.message("546")
			if (game.one_in(5)) then
				DEBUG.add_msg("mutate!")
	log.message("549")
				add_msg("魔性の体液が"..target:disp_name().."の体を変異させる...", H_COLOR.YELLOW)
				target:mutate_category("FIEND")
	log.message("552")
			end
		end

	log.message("553")
		--妊娠チェック。やればできる。
		DEBUG.add_msg("preg roll-->")
		--TODO:共通化したい

		if not(target.male) then
			local mtype = monster.type
			if (mtype:in_species(species_id("MALE")) or mtype:in_species(species_id("HERM"))) then
				DEBUG.add_msg("male and female.")
				DEBUG.add_msg("mtype.id : "..mtype.id:str())

				if (is_cubi(monster)) then
					target:set_value(CREAMPIE_SEED_TYPE, "FIEND")
				else
					target:set_value(CREAMPIE_SEED_TYPE, "HUMAN")
				end

				--判定に成功すれば孕む。失敗しても5日くらい溜まったままにする。
				if (preg_roll(target)) then
					DEBUG.add_msg("impregnated.")
					target:add_effect(efftype_id("impregnated"), game.get_time_duration(1), "num_bp", true)
				else
					DEBUG.add_msg("not impregnate.")
					target:add_effect(efftype_id("creampie"), game.get_time_duration(72000))
				end

			end
		end

	log.message("582")
		--一時的に友好的に近づける。でないとrape loopに嵌ってしまう...
		--TODO:それでも複数に囲まれると死ぬまで嬲られるのをどうにかしたい
		monster.anger = monster.anger - 50
		monster.friendly = monster.friendly + 50
		monster.morale = monster.morale - 30

	log.message("589")
		--モンスターから"dominate"を外し、対象からも"gotwifed"のintensityを1つ下げる。
		monster:remove_effect(efftype_id("dominate"))
		target:add_effect(efftype_id("gotwifed"), game.get_time_duration(-1), "num_bp", true)
	end

	log.message("595")
	--DEBUG.add_msg("wife you!")

	target:mod_moves(-100)
	monster:mod_moves(-100)

	return
end

--[[ハート炎攻撃]]--
function matk_loveflame(monster)
	local max_range = 15		--特殊攻撃の最大射程

	DEBUG.add_msg("love flame?")

	--攻撃しようとしているターゲットを取得
	local target = get_attackable_target(monster, max_range)

	if (target == nil) then
		return
	end

	--ここでモンスターに行動コストを追加。
	monster:mod_moves(-200)

	local tripoint_list = LoveFormula(target:pos(), 15, 0.6)

	for key, value in pairs(tripoint_list) do
		--DEBUG.add_msg("key:"..key)
		--DEBUG.add_msg("value:"..value.x..", "..value.y)
		map:add_field(value, "fd_fire", 10, game.get_time_duration(10))
		--g:draw()
		--g:draw_ter(value)
	end

	game.add_msg(monster:disp_name().."が呪文を唱えると、"..target:disp_name().."の周りを炎が覆った！")

	DEBUG.add_msg("love flame!")
end

--[[露出攻撃。...攻撃？]]--
function matk_expose(monster)
	local max_range = 30		--特殊攻撃の最大射程

	DEBUG.add_msg("expose?")

	--周辺に存在するplayerをすべて取得する
	local player_list = get_players()
	local target_list = {}

	local dist
	for key, value in pairs(player_list) do

		DEBUG.add_msg("key:"..key)
		DEBUG.add_msg("value:"..value:disp_name())

		dist = game.distance(monster:posx(), monster:posy(), value:posx(), value:posy())		--line.cppを参照。

		--対象が射程内であればリストに追加する
		if (dist <= max_range) then
			table.insert(target_list, value)
		end

	end

	--対象が存在するなら
	if (#target_list > 0) then
		--ここでモンスターに行動コストを追加。
		monster:mod_moves(-500)

		if (player:sees(monster:pos())) then
			local rnd = math.random(3)

			if (rnd == 1) then
				add_msg(monster:disp_name().."は衣服を脱ぐと、艶やかな裸体を周囲に見せ付け始めた！", H_COLOR.PINK)
			elseif (rnd == 2) then
				add_msg(monster:disp_name().."は自らの全てを見せつけながら、甘い声で皆を誘った！", H_COLOR.PINK)
			else
				add_msg(monster:disp_name().."は股間に手を当てると、切なげな声を上げ始めた！", H_COLOR.PINK)
			end

		end

		for key, value in pairs(target_list) do
			--対象にモンスターの姿が見ているなら"lust"と"corrupt"を与える。
			if (value:sees(monster:pos())) then
				--value:add_effect(efftype_id("corrupt"), game.get_time_duration(600))
				gain_corrupt(value, 600)
				value:add_effect(efftype_id("lust"), game.get_time_duration(24))
			end
		end
	end

	DEBUG.add_msg("expose!")
end

--[[↓こっからは魔法系統↓]]--

--[[沈静ガス攻撃]]--
function magic_fire_circle(monster, target)
	DEBUG.add_msg("relax_circle?")

	local tripoint_list = LoveFormula(target:pos(), 3, 0)
	for key, value in pairs(tripoint_list) do
		map:add_field(value, "relax_gas", 3, game.get_time_duration(20))
	end

	if (player:sees(monster:pos())) then
		game.add_msg(monster:disp_name().."が呪文を唱えると、甘いガスが"..target:disp_name().."の周りを覆った！")
	end

	DEBUG.add_msg("relax_circle!")

end

--[[火の輪攻撃]]--
function magic_fire_circle(monster, target)
	DEBUG.add_msg("fire_circle?")

	local tripoint_list = LoveFormula(target:pos(), 6, 0)
	for key, value in pairs(tripoint_list) do
		map:add_field(value, "fd_fire", 1, game.get_time_duration(10))
	end

	if (player:sees(monster:pos())) then
		game.add_msg(monster:disp_name().."が呪文を唱えると、炎が"..target:disp_name().."の周りを覆った！")
	end

	DEBUG.add_msg("fire_circle!")

end

--[[睡眠攻撃]]--
function magic_sleep(monster, target)
	DEBUG.add_msg("sleep you?")

	if (player:sees(monster:pos())) then
		game.add_msg(monster:disp_name().."は"..target:disp_name().."を指差して呪った！")
	end
	target:add_effect(efftype_id("magic_sleepy"), game.get_time_duration(900))

	DEBUG.add_msg("sleep you!")

end

--[[引き寄せ攻撃]]--
function magic_pull_close(monster, target)
	DEBUG.add_msg("pull_close you?")

	--引き寄せる場所の候補リストを取得。
	local locate_list = get_around_empty_locs(monster:pos())
	if (#locate_list == 0) then
		--万が一引き寄せる場所が無い場合は何もしない。
		return
	end

	--場所の候補からランダムで1つ選択し、対象の位置を移動させる。
	local locale = locate_list[math.random(#locate_list)]
	target:setpos(locale)
	game.add_msg(target:disp_name().."はテレポートした！")

	DEBUG.add_msg("pull_close you!")

end

--[[魔法陣描き攻撃。魔法陣に限らずトラップ全般に使える？]]--
function magic_write_circle(monster, tripoint, str_id)
	DEBUG.add_msg("write_circle?")

	local tmp = map:tr_at(tripoint)
	--NOTE:trapオブジェクトの実態はint型のため、「:str()」を付けてstring型の名称を取得してやる。
	--対象場所に既に別のトラップが存在するなら何もしない。
	if not(tmp.id:str() == "tr_null") then
		DEBUG.add_msg("error:another trap is already here.")
		return
	end

	--描き対象の魔法陣trapを取得する。("trap_str_id"メタテーブル)
	local circle = trap_str_id(str_id)

	--トラップを設置する。
	map:trap_set(tripoint, circle:id())	--trapオブジェクトの実態であるint型は「:id()」で取得できる。

	if (player:sees(monster:pos())) then

		if (player:sees(tripoint)) then
			game.add_msg(monster:disp_name().."は地面に魔法陣を描いた！")
		end

	else
		if (player:sees(tripoint)) then
			game.add_msg("突如として地面に魔法陣が現れた！")
		end
	end

	DEBUG.add_msg("write_circle!")

end

--[[魔法詠唱]]--
function spell_charge(monster)
	DEBUG.add_msg("spell_charge-->")

	monster:add_effect(efftype_id("spell_charge"), game.get_time_duration(EFF_SPELL_CHARGE_INT_FACTOR))

	if (player:sees(monster:pos())) then
		if (monster:has_effect(efftype_id("spell_charge"))) then
			game.add_msg(monster:disp_name().."は呪文を唱えている...")
		else
			game.add_msg(monster:disp_name().."は呪文を唱え始めた。")
		end

	else
		game.add_msg("誰かのささやき声が聞こえる...")
	end

	--monster:add_effect(efftype_id("spell_charge"), game.get_time_duration(1), "num_bp", true)

end

--[[魔法陣（召喚）の起動]]--
function magic_circle_summon(tripoint, monster_id_list)
	DEBUG.add_msg("circle_summon?")

	local trap = map:tr_at(tripoint)

	--NOTE:trapオブジェクトの実態はint型のため、「:str()」を付けてstring型の名称を取得してやる。
	--DEBUG.add_msg("trap.id:"..trap.id:str())

	--trapが無ければ何もしない。
	if (trap.id:str() == "tr_null") then
		return
	end

	--トラップ"tr_magic_circle_summon"が指定位置にある場合は起動する。
	if (trap.id:str() == "tr_magic_circle_summon") then

		--...魔法陣の上に何かが乗っている場合は出てこれない。
		if not(g:is_empty(tripoint)) then
			if (player:sees(tripoint)) then
				game.add_msg("魔法陣の中から何者かが現れようとしましたが、障害物に阻まれました。")
			end
			return
		end

		--起動した魔法陣は取り除く。
		map:remove_trap(tripoint)

		--monster_id_listの中からランダムで対象を選択し、召喚する。
		local mon = game.create_monster(mtype_id(monster_id_list[math.random(#monster_id_list)]), tripoint)

		if (player:sees(tripoint)) then
			game.add_msg("魔法陣の中から何者かが現れた！")
		end

		--召喚して即動かれるとゲームバランス的にあれなので、ここで召喚したモンスターに行動コストを追加。
		mon:mod_moves(-200)

	end

	DEBUG.add_msg("circle_summon!")

end

--[[魔法陣（火炎）の起動]]--
function magic_circle_fire(tripoint, density, age)
	DEBUG.add_msg("circle_fire?")

	local trap = map:tr_at(tripoint)

	--NOTE:trapオブジェクトの実態はint型のため、「:str()」を付けてstring型の名称を取得してやる。
	--DEBUG.add_msg("trap.id:"..trap.id:str())

	--trapが無ければ何もしない。
	if (trap.id:str() == "tr_null") then
		return
	end

	--トラップ"tr_magic_circle_fire"が指定位置にある場合は起動する。
	if (trap.id:str() == "tr_magic_circle_fire") then

		--起動した魔法陣は取り除く。
		map:remove_trap(tripoint)

		--火を発生させる。
		map:add_field(tripoint, "fd_fire", density, game.get_time_duration(age))

		if (player:sees(tripoint)) then
			game.add_msg("魔法陣から炎が噴出した！")
		end
	end

	DEBUG.add_msg("circle_fire!")

end

--[[↓"どのモンスターが何の魔法をどのくらいの溜め・威力で使えるかを個別に設定する"↓]]--

--[[サキュバス・ソムノ]]--
function matk_magic_succubi_somnophilia(monster)
	local max_range = 20		--特殊攻撃の最大射程

	DEBUG.add_msg("succubi_somnophilia magic-->")

	--攻撃しようとしているターゲットを取得
	local target = get_attackable_player(monster, max_range)

	if (target == nil) then
		return
	end

	--ターゲットが既に効果に掛かっている、あるいは寝ているなら何もしない。
	if (target:has_effect(efftype_id("magic_sleepy")) or target:has_effect(efftype_id("sleep"))) then
		DEBUG.add_msg("Welp, slept already.")
		return
	end

	local magic_sleep_cost = 3	--睡眠魔法の実行コスト

	--"spell_charge"のintensityを取得
	local intensity = monster:get_effect_int(efftype_id("spell_charge"))
	DEBUG.add_msg("spell_charge intensity: "..intensity)

	if (intensity >= magic_sleep_cost) then
		DEBUG.add_msg("Let's cast!")
		--intensityが規定値に到達していれば発動！
		magic_sleep(monster, target)
		monster:remove_effect(efftype_id("spell_charge"))

		monster:mod_moves(-500)

	else
		DEBUG.add_msg("Charging...")
		--足りなければ詠唱を続ける
		spell_charge(monster)
		monster:mod_moves(-150)
	end

end

--[[ダークウィッチ]]--
function matk_magic_dark_witch(monster)
	local max_range = 20		--特殊攻撃の最大射程

	DEBUG.add_msg("dark_witch magic-->")

	--攻撃しようとしているターゲットを取得
	local target = get_attackable_player(monster, max_range)

	if (target == nil) then
		return
	end

	local magic_fire_cost = 3	--魔法の実行コスト

	--"spell_charge"のintensityを取得
	local intensity = monster:get_effect_int(efftype_id("spell_charge"))
	DEBUG.add_msg("spell_charge intensity: "..intensity)

	if (intensity >= magic_fire_cost) then
		DEBUG.add_msg("Let's cast!")
		--intensityが規定値に到達していれば発動！
		magic_fire_circle(monster, target)
		monster:remove_effect(efftype_id("spell_charge"))

		monster:mod_moves(-300)

	else
		DEBUG.add_msg("Charging...")
		--足りなければ詠唱を続ける
		spell_charge(monster)
		monster:mod_moves(-150)
	end

end

--[[山羊頭の悪魔]]--
function matk_magic_goathead_demon(monster)
	local max_range = 10	--特殊攻撃の最大射程
	local magic_cost = 1	--魔法の実行コスト

	DEBUG.add_msg("goathead_demon magic-->")

	--攻撃しようとしているターゲットを取得
	local target = get_attackable_player(monster, max_range)

	if (target == nil) then
		return
	end

	local magic_cost = 1	--魔法の実行コスト

	--"spell_charge"のintensityを取得
	local intensity = monster:get_effect_int(efftype_id("spell_charge"))
	DEBUG.add_msg("spell_charge intensity: "..intensity)

	if (intensity >= magic_cost) then
		DEBUG.add_msg("Let's cast!")
		--intensityが規定値に到達していれば発動！

		if (game.one_in(4)) then
			--周囲にある魔法陣（召喚）を起動する。
			local locs = get_around_locs(monster:pos(), 0, 10)
			for key, value in pairs(locs) do
				magic_circle_summon(value, MON_ARCH_CUBI_LIST)
			end

		else
			--魔法陣（召喚）を設置する。
			local locate_list = get_around_empty_locs(player:pos())
			magic_write_circle(monster, locate_list[math.random(#locate_list)], "tr_magic_circle_summon")
		end

		monster:remove_effect(efftype_id("spell_charge"))
		monster:mod_moves(-300)

	else
		DEBUG.add_msg("Charging...")
		--足りなければ詠唱を続ける
		spell_charge(monster)
		monster:mod_moves(-150)
	end

end


code:
--[[山羊頭の悪魔のイベント]]--
function event_goathead_demon(monster)
	local someone = monster:attack_target()

	--相手がいなければ何もしない。
	if (someone == nil) then
		return
	end
	--姿がプレイヤーに見えていなければ何もしない。相手に姿を見せてから名乗りをあげないとただの変な奴だし。
	if not(player:sees(monster:pos())) then
		return
	end

	--イベント発生用の特殊攻撃を無効化する。
	monster:disable_special("EVENT_GOATHEAD_DEMON")

	--TALK
	if (player:get_value(EVENT_GOATHEAD_DEMON)  == "met") then
		--会ったフラグがある場合のセリフ。激おこ。
		game.popup("「・・・貴様は！おのれ、一体どれだけ我等の邪魔をすれば気が済むのだ！\r\n貴様だけは絶対に許さん！ただ嬲り殺すだけでは飽き足らぬ、地獄の底で永遠の責め苦を与えてやる！」")

	else
		--会ったフラグが無い場合のセリフ。
		game.popup("「これはこれは・・・何やら騒々しいとは思ったが、無関係の人間が現れるとは。\r\n英雄気取りか、好奇心に誘われたか・・・どちらにせよ愚か者には変わりあるまい。」")
		game.popup("「このサバトは我等悪魔が地上を手中に収めんが為の重要な儀。\r\n貴様が何者かは知らんが、邪魔されるわけにはいかぬ。\r\n貴様には此処で死んでもらう。」")

		if (player.male) then
			game.popup("「案ずるな。貴様の魂を奪った後は、我が配下の悪魔として生まれ変わらせてやろう！\r\nハーッハッハッ！」")
		else
			game.popup("「よくよく見れば中々の上玉、活力に満ちた肉体の持主よ。\r\nよろしい、貴様の魂を奪った後は、我がサバトの贄として存分に利用してやろう！\r\nハーッハッハッ！」")
		end

		--会ったフラグを立てる。
		player:set_value(EVENT_GOATHEAD_DEMON, "met")
	end

	--周囲にある魔法陣を起動する。
	local locs = get_around_locs(monster:pos(), 0, 10)
	for key, value in pairs(locs) do
		--DEBUG.add_msg("point.x:"..value.x)
		--DEBUG.add_msg("point.y:"..value.y)

		magic_circle_fire(value, 3, 30)
		magic_circle_summon(value, MON_ARCH_CUBI_LIST)
	end

	monster:mod_moves(-100)

end

--[[NPC"demonbeing_schoolgirl"を呼び出すイベント]]--
--[[
	NOTE:NPCを配置する場合、そのNPCにユニーク名称を付けなければ性別を固定することができない。
	しかしそうすると同一人物が複数存在する事になってしまう...そのため、「その人物に会った事があるかあるかどうか」をフラグとして管理し、
	会っていない場合はNPCを生成、既に会っている場合はモンスターを生成する仕様にする。
]]--
function event_demonbeing_schoolgirl(monster)

	--イベント発火地点を保持しておく。(ダミーモンスターを消すとnil参照で落ちるため)
	local tripoint = monster:pos()

	--なんかよくわからないけど、プレイヤーとの距離が離れすぎているとアボートするっぽい？ので、プレイヤーが近くにいない場合は処理を抜ける。
	local dist = game.distance(tripoint.x, tripoint.y, player:posx(), player:posy())		--line.cppを参照。
	if (dist > 10) then
		--DEBUG.add_msg("Out of range.")
		return
	end

	--ダミーモンスターは消す。
	g:remove_zombie(monster)

	if (player:get_value(EVENT_DEMONBEING_SCHOOLGIRL)  == "met") then
		--既に会ってる場合はモンスターを生成。
		local mon = game.create_monster(mtype_id("mon_corrupted_schoolgirl"), tripoint)

	else
		--まだ会ってない場合はNPCを生成。
		local npc_id = map:place_npc(tripoint.x, tripoint.y, npc_template_id("demonbeing_schoolgirl"))

		--会ったフラグを立てる。
		player:set_value(EVENT_DEMONBEING_SCHOOLGIRL, "met")
	end

end


code:
--[[いろいろ]]--

DEBUG = {
	--trueにセットするとデバッグメッセージを表示する
	enabled = false
}

--[[デバッグ用ファンクション]]--
DEBUG.hello_world = function()
	game.add_msg("Hello World!")
end

DEBUG.add_msg = function(message)
	if (DEBUG.enabled) then
		game.add_msg(message)
	end
end

DEBUG.chk_calendar = function()

	local cldr = game.get_calendar_turn()

	DEBUG.add_msg("year_length:"..cldr:year_length():get_turns())		--ターン数基準
	DEBUG.add_msg("season_length:"..cldr:season_length():get_turns())	--ターン数基準
	DEBUG.add_msg("season_ratio:"..cldr:season_ratio())					--現実世界の1季節の日数(91日)に対するゲーム内の季節の長さの比率。たとえばゲーム内1季節を14日に設定した場合、14 / 91 = 約0.1538になる。
	DEBUG.add_msg("season_from_default_ratio:"..cldr:season_from_default_ratio())	--ゲーム内のデフォルトの1季節の日数(14日)に対する現在の1季節日数の比率。たとえばゲーム内1季節を28日に設定した場合 28 / 14 = 2になる。
	DEBUG.add_msg("day_of_year:"..cldr:day_of_year())
	DEBUG.add_msg("get_turn:"..cldr:get_turn())
	DEBUG.add_msg("sunlight:"..cldr:sunlight())
	DEBUG.add_msg("years:"..cldr:years())

end

--[[メッセージを指定した色で表示するだけ]]--
function add_msg(message, h_color)

	if(h_color == nil) then
		game.add_msg(message)
	else
		game.add_msg("<color_"..h_color..">"..message.."</color>")
	end

end

--[[周囲に存在するcritterのリストを取得する。]]--
function get_critters()

	local critter_list = {}

	local ids
	ids = g:num_creatures()		--reality bubble(時間が進む範囲)に存在するcreatureの最大idを取得する。（ただし既に死んだcreatureのゴミ情報を含む場合もあるらしい）

	local chara
	for i = 1, ids, 1 do
		chara = g:critter_by_id(i)	--idをキーに生きているcritterを取得する。...ただし今の所取得できるのはplayerとnpcのみらしい

		if (chara == nil) then
			DEBUG.add_msg("id"..i..":なし")
		else
			DEBUG.add_msg("id"..i..":"..chara:disp_name())
			table.insert(critter_list, chara)
		end

	end

	return critter_list
end

--[[周囲に存在するplayer（NPC含む）のリストを取得する。]]--
function get_players()

	local critter_list = {}
	local player_list = {}

	--周辺に存在するcritterをすべて取得する
	critter_list = get_critters()

	for key, value in pairs(critter_list) do
		--DEBUG.add_msg("key:"..key)
		--DEBUG.add_msg("value:"..value:disp_name())

		--対象がplayerおよびnpcであればリストに追加する		取得されるのはplayerとNPCだけなんだけど、いつ仕様変更が入るのかわからないので念のため
		if (value:is_player() or value:is_npc()) then
			--HACK:ここでplayerクラスに変換しておく。
			--table.insert(player_list, g:player_at(value:pos()))
			if value:is_player() then
				table.insert(player_list, player)
			else
				table.insert(player_list, game.get_npc_at(value:pos()))
			end
		end

	end

	return critter_list
end

--[[Characterが着用しているアイテムのリストを取得する。body_partが指定されている場合はその部分を覆うもののみ対象。]]--
function get_wears(chara, body_part)
	local item_list = {}

	local i = -2		--i_at(int)で-2以下を指定するとCharacterが着用しているitemを取得できる。ちなみに-1は装備武器、0以上が所持品。
	local item = chara:i_at(i)

	--着用itemを順次取得してリストに追加する。
	--NOTE:i_at(int)で存在しない番号を指定すると、nilではなく"なし"アイテムを返すため、本当にそのアイテムを着用しているかどうかで判断する。
	while (chara:is_worn(item)) do

		if (body_part == nil or item:covers(body_part)) then
			--DEBUG.add_msg(item:typeId())
			--DEBUG.add_msg(item:display_name())
			table.insert(item_list, item)
		end

		i = i - 1
		item = chara:i_at(i)

	end

	return item_list
end

--[[Characterが着用しているアイテムの中からランダムに1つ取得する。body_partが指定されている場合はその部分を覆うもののみ対象。]]--
function get_random_wear(chara, body_part)
	local item_list = get_wears(chara, body_part)
	if item_list then
		return item_list[math.random(#item_list)]
	else
		return nil
	end
end

--[[Characterが所持している(インベントリ内の)アイテムのリストを取得する。]]--
function get_items(chara)
	local item_list = {}

	local i = 0		--i_at(int)で0以上を指定するとCharacterが所持しているitemを取得できる。
	local item = chara:i_at(i)

	--所持itemを順次取得してリストに追加する。
	--NOTE:i_at(int)で存在しない番号を指定すると、nilではなく"なし"アイテムを返すため、本当にそのアイテムを所持しているかどうかで判断する。
	while (chara:has_item(item)) do

		--DEBUG.add_msg(item:typeId())
		--DEBUG.add_msg(item:display_name())
		--table.insert(item_list, item)
		--typeIdをキー、itemをvalueにする
		item_list[item:typeId()] = item

		i = i + 1
		item = chara:i_at(i)

	end

	return item_list
end

--[[アイテムのidをキーにCharacterが所持している(インベントリ内の)アイテムオブジェクトを取得する。]]--
function get_item_by_id(chara, str_id)
	local dummy_item = item("null", 1)

	local item_list = get_items(chara)

	if not(item_list[str_id] == nil) then
		return item_list[str_id]
	else
		return dummy_item
	end

end

--[[中心点の周囲8マスの中で誰もいないマスのリストを取得する]]--
function get_around_empty_locs(center)
	--DDA同梱のサンプルmodを流用。
	local locs = {}

	for delta_x = -1, 1 do
		for delta_y = -1, 1 do
			local point = center
			point.x = point.x + delta_x
			point.y = point.y + delta_y
			if (g:is_empty(point)) then
				--DEBUG.add_msg("x: "..point.x)
				--DEBUG.add_msg("y: "..point.y)
				table.insert(locs, point)
			end
		end
	end

	return locs

end

--[[指定した範囲内のtripointリストを取得する。]]--
function get_around_locs(center, min_radius, max_radius)

	local locs = {}

	--ひどいネストだ...
	for ix = center.x + (-1 * max_radius), center.x + max_radius do
		if (ix <= -1 * min_radius or ix >= min_radius) then
			for iy = center.y + (-1 * max_radius), center.y + max_radius do
				if (iy <= -1 * min_radius or iy >= min_radius) then
					local point = tripoint(ix, iy, center.z)
					table.insert(locs, point)
				end
			end
		end
	end

	return locs
end

--classesに独自function実装できないかなーと弄ってうまくいかなかった残骸
--MyPlayer = {}
--MyPlayer.new = function(creature)
--	local this = debug.setmetatable(creature, debug.getmetatable(player))
--
--	this.sayHello = function(self)
--		DEBUG.add_msg("Hello, I'm player!")
--	end
--
--	return this
--
--end
--
--function iuse_hoge(item, active)
--
--	DEBUG.add_msg("hoge")
--
--	local center = player:pos()
--	local selected_x, selected_y = game.choose_adjacent("誰に対して使用しますか？", center.x, center.y)
--	local selected_point = tripoint(selected_x, selected_y, center.z)
--
--	local someone = g:critter_at(selected_point)
--
--	DEBUG.add_msg(someone:disp_name())
--
--	local hoge = MyPlayer.new(someone)
--
--	hoge:sayHello()
--
--end
--
--
--function iuse_hoge(item, active)
--
--	DEBUG.add_msg("hoge")
--
----	local center = player:pos()
----	local selected_x, selected_y = game.choose_adjacent("誰に対して使用しますか？", center.x, center.y)
----	local selected_point = tripoint(selected_x, selected_y, center.z)
----
----	local someone = g:critter_at(selected_point)
----
----	DEBUG.add_msg(someone:disp_name())
----
----	someone.hello()
--
--end
--
--function fuga()
--
--	local center = player:pos()
--	local selected_x, selected_y = game.choose_adjacent("誰に対して使用しますか？", center.x, center.y)
--	local selected_point = tripoint(selected_x, selected_y, center.z)
--
--	local someone = g:critter_at(selected_point)
--
--	DEBUG.add_msg(someone:disp_name())
--
--	someone.hello(someone)
--end
--
--classes.Creature.hello = function(self)
--	DEBUG.add_msg("hello, I'm Creature!")
--	DEBUG.add_msg(Creature.disp_name())
--	DEBUG.add_msg(self.disp_name())
--end
--
--classes.Character.hello = function(self)
--	DEBUG.add_msg("hello, I'm Character!")
--	DEBUG.add_msg(Character.disp_name())
--	DEBUG.add_msg(self.disp_name())
--end
--
--classes.monster.hello = function(self)
--	DEBUG.add_msg("hello, I'm monster!")
--	DEBUG.add_msg(monster.disp_name())
--	DEBUG.add_msg(self.disp_name())
--end
--
--classes.player.hello = function(self)
--	DEBUG.add_msg("hello, I'm player!")
--	DEBUG.add_msg(player.disp_name())
--	DEBUG.add_msg(self.disp_name())
--end


code:
local MOD = {
	id = "Hentai_mod"
}
mods[MOD.id] = MOD

--[[新規プレイスタート時]]--
function MOD.on_new_player_created()

	DEBUG.add_msg("on_new_player_created-->")

	flagitem = get_item_by_id(player, "fake_prof_pet")

	--profession"一人と一匹"開始時（というか判定用ダミーアイテムを所持していた場合）の処理。
	if (player:has_item(flagitem)) then
		DEBUG.add_msg("man_with_a_pet-->TRUE")
		--DEBUG.add_msg("flagitem:"..flagitem:display_name())
		player:i_rem(flagitem)		--ダミーアイテムはもちろん没収。

		--ペット選択メニューを表示。
		local menu = game.create_uimenu()
		local choice = -1
		menu.title = PROF_PET_LIST.TITLE

		for key, value in pairs(PROF_PET_LIST.LIST_ITEM) do
			menu:addentry(value.ENTRY)
		end

		menu:query(true)
		choice = menu.selected
		DEBUG.add_msg("choice:"..choice)

		--選択したリストに対応するペットとボーナスアイテムをそれぞれ配布。
		local locate_list = get_around_empty_locs(player:pos())
		if (#locate_list == 0) then
			--万が一ペットがスポーンする場所が無い場合はプレイヤーの位置に配置する。そんな事そうそう無いと思うが...
			table.insert(locate_list, player:pos())
		end

		DEBUG.add_msg("pet_id:"..PROF_PET_LIST.LIST_ITEM[choice+1].PET_ID)
		local pet = game.create_monster(mtype_id(PROF_PET_LIST.LIST_ITEM[choice+1].PET_ID), locate_list[math.random(#locate_list)])
		--local pet = game.create_monster(mtype_id("mon_dog"), locate_list[math.random(#locate_list)])
		pet.friendly = -1
		pet:add_effect(efftype_id("pet"), game.get_time_duration(1), "num_bp", true)

		--ペットがサキュバスの場合は特殊攻撃"WIFE_U"を無効化しておく。処理が重くなるし、何よりペットが敵対NPCに寝取られるのは嫌でしょうから。（ええと、自分から仕掛けてるから寝取る？寝取りられる？）
		if (pet.type.id == mtype_id("mon_succubi")) then
			--ちなみに特殊攻撃を持っていないmonsterに対してdisable_special()を行うとランタイムエラーを起こしてCTDする。ゲームは死ぬ。
			pet:disable_special("WIFE_U")
		end

		DEBUG.add_msg("pet:"..pet:disp_name())

		for key, value in pairs(PROF_PET_LIST.LIST_ITEM[choice+1].BONUS_ITEM) do
			player:i_add(item(value, 1))
		end

		player:mod_moves(-200)

	end

	DEBUG.add_msg("-->end on_new_player_created")

end

--[[ゲーム内で日付が変わった際(真夜中！)のコールバック]]--
function MOD.on_day_passed()

	--周辺に存在するplayerをすべて取得する
	local player_list = get_players()

	for key, value in pairs(player_list) do
		preg_process(value)
	end

end

--[[ゲーム内で1時間経過した際のコールバック]]--
function MOD.on_hour_passed()

	--周辺に存在するplayerをすべて取得する
	local player_list = get_players()

	for key, value in pairs(player_list) do
		birth_process(value)

		--HACK:バグで"gotwifed"が残ったままになってしまった場合の対処としてここで消しておく。
		value:remove_effect(efftype_id("gotwifed"))
	end

end

--[[ゲーム内で1ターン経過した際のコールバック]]--
function MOD.on_turn_passed()
	if player:has_activity(activity_id("ACT_SEX")) then
		if player.activity.moves_left < 1000 then
			SEX.act_sex_finish(player.activity, player)
			player.activity:set_to_null()
		else
			SEX.act_sex_do_turn(player.activity, player)
		end
	end
end

--[[ミッションをクリアした際のコールバック]]--
MOD.on_player_mission_finished = function(player_id, mission_id)
	DEBUG.add_msg("mission_finished-->")
	DEBUG.add_msg("mission_id:"..mission_id)
	DEBUG.add_msg("player_id:"..player_id)

end

--[[妊娠関連の処理を行う]]--
--TODO:reality bubble内にいないNPCに対して経過日数の処理が行えない。最後に計測した日を持っておいて、次回の計測時に差分をまとめて計上する？
function preg_process(mother)
	DEBUG.add_msg("---preg_process---")
	DEBUG.add_msg("mother:"..mother:get_name())

	--カレンダーを基準に経過日数を求める
	local cldr = game.get_calendar_turn()
	local elapse_day = 1 / cldr:season_ratio()				--ゲーム内1季節の長さの比率で割る事で、現実時間単位での経過日数を求めることができる

	--季節変動が無効なら経過日数はリアル日数基準にしておく（現実で季節が変わらないなんてありえないけどね！）
	if (cldr:eternal_season()) then
		elapse_day = 1
	end

	DEBUG.add_msg("elapse_day:"..elapse_day)

	--TODO:父親の種類や母親の特質などによって成長速度を変化できるようにする
	local preg_speed_ratio = DEFAULT_PREG_SPEED_RATIO
	DEBUG.add_msg("preg_speed_ratio:"..preg_speed_ratio)

	--pregnantcy状態の処理
	if (mother:has_effect(efftype_id("pregnantcy"))) then
		DEBUG.add_msg("Process pregnantcy ->")

		mother:add_effect(efftype_id("pregnantcy"), game.get_time_duration(elapse_day), "num_bp", true)

	--impregnated状態の処理
	elseif (mother:has_effect(efftype_id("impregnated"))) then
		DEBUG.add_msg("Process impregnated ->")

		local timecount
		timecount = mother:get_effect_dur(efftype_id("impregnated"))
		--DEBUG.add_msg("timecount:"..timecount:get_turns())
		--NOTE:timecount:get_turns()はLUA拡張版DDA独自の記述であって、本家DDAには実装されていない...

		--NOTE:が、timecountは__eqエントリーを持っているのでオブジェクト同士での比較が可能。
		--if (timecount:get_turns() < 70) then
		if not(timecount == game.get_time_duration(70)) then
			mother:add_effect(efftype_id("impregnated"), game.get_time_duration(elapse_day), "num_bp", true)

		else
			--impregnated状態が最大値に届いているならpregnantcy状態に移行する
			game.popup(mother:get_name().."の様子がおかしい...")
			game.popup("どうやら"..mother:get_name().."は子供を身篭ったようだ。")

			mother:remove_effect(efftype_id("impregnated"))
			mother:add_effect(efftype_id("pregnantcy"), game.get_time_duration(elapse_day), "num_bp", true)
		end

	--creampie状態なら妊娠チェック。
	elseif (mother:has_effect(efftype_id("creampie"))) then
		DEBUG.add_msg("Process creampie ->")

		--判定に成功すれば孕む。
		if (math.random(100) <= preg_roll(mother)) then
			DEBUG.add_msg("impregnated.")
			mother:remove_effect(efftype_id("creampie"))
			mother:add_effect(efftype_id("impregnated"), game.get_time_duration(1), "num_bp", true)
		end

	else
		DEBUG.add_msg("Process other ->")
		--妊娠してない場合
		--TODO:ここに発情や抱卵の処理を追加する

		--calendarから現在の季節を取得する方法が無いので（見逃してるだけかもしれないけど）、ゲーム内1季節の長さをデフォルトの季節の長さとの比率で割ることで相対的に求める。
		local day_by_default = cldr:day_of_year() / cldr:season_from_default_ratio()
		DEBUG.add_msg("day_by_default:"..day_by_default)

		--狼は冬の終わりごろから2～3週間にかけて発情期となる。（リアル世界の話です）
		if (mother:has_trait(trait_id("ESTRUS_LUPINE"))) then
			DEBUG.add_msg("Lupine estrus ->")

			--TODO:判定がガバい。ゲーム内日数で判断したい
			if (day_by_default >= 0 and day_by_default <= 11) then
				add_msg(mother:disp_name().."に発情期が訪れました。", H_COLOR.PINK)
				mother:add_effect(efftype_id("estrus"), game.get_time_duration(14400))
			end
		end

		--猫は春先から秋の終わりにかけ、2週間おきに4～7日間の発情期となる。（リアル世界の話です）
		if (mother:has_trait(trait_id("ESTRUS_FELINE"))) then
			DEBUG.add_msg("Feline estrus ->")

			--TODO:判定がガバい。ゲーム内日数で判断したい
			if ((day_by_default >= 3 and day_by_default <= 7) or (day_by_default >= 17 and day_by_default <= 21) or (day_by_default >= 31 and day_by_default <= 35)) then
				add_msg(mother:disp_name().."に発情期が訪れました。", H_COLOR.PINK)
				mother:add_effect(efftype_id("estrus"), game.get_time_duration(14400))
			end
		end

	end

end

--[[出産処理を行う]]--
function birth_process(mother)
	DEBUG.add_msg("---birth_process---")

	--pregnantcy状態の処理
	if (mother:has_effect(efftype_id("pregnantcy"))) then
		local timecount

		timecount = mother:get_effect_dur(efftype_id("pregnantcy"))
		--DEBUG.add_msg("timecount:"..timecount:get_turns())

		--pregnantcy状態が最大値に届いているなら確率で生まれる。
		--if (timecount:get_turns() >= 210) then
		if (timecount == game.get_time_duration(210)) then

			--一日に3回程度、つまり3/24の確率で出産チェックを通過する。確率は適当。
			if (math.random(100) > 12) then
				mother:mod_pain(25)
				add_msg(mother:disp_name().."は苦しんでいます！", H_COLOR.RED)
				return
			end

			--周囲にスポーンする場所がなければ処理を抜ける。
			local locate_list = get_around_empty_locs(mother:pos())
			if (#locate_list == 0) then
				return
			end

			local locate = locate_list[math.random(#locate_list)]

			game.popup(mother:get_name().."は子供を産み落としました！")
			mother:remove_effect(efftype_id("pregnantcy"))

			local child_id = map:place_npc(locate.x, locate.y, npc_template_id("darkdays_children"))
			--game.create_monster(mtype_id("debug_mon"), locate_list[math.random(#locate_list)])
			DEBUG.add_msg("child_id:"..child_id)

			game.popup("そのとき不思議なことが起こり、子供は一瞬で成長しました。")
			--どうすりゃいいのさ、その...ゲームシステムというか仕様を...
			--リアルタイム換算で成長させようとすると数年単位かかって超めんどくさいし、monster化するとlua制御が無理だし、かといって時間経過で変化するアイテムで代用するのはちょっと...

			--TODO:本当はこの場でNPCの名前を変更したりしたいんだけど無理なので名前の巻物を与えてお茶をにごす
			local scroll = item("scroll_of_naming", 1)
			map:add_item(player:pos(), scroll)
			add_msg("あなたの足元に何かが転がってきた。", H_COLOR.GREEN)

			----TODO:生成したNPCに対してこのタイミングで取得・操作する方法を考える。
			----NOTE:map:place_npc()で生成したNPCは一度overmapのbufferに追加されてから次ターンにキャラとして生成されるため、単純にplace_npc()で指定したlocateを元にこのタイミングで取得しようとしてもまだ"生まれていない"。先行して発番されたidを元にNPCを取得しようとしても無理。
			--local assign_id = g:assign_npc_id()
			--DEBUG.add_msg("assign_id:"..assign_id)
            --
			--local childname = game.string_input_popup("子供の名前を入力してください。", 0, "")
			--if (childname == "") then
			--	childname = DEFAULT_NPC_NAME
			--end
			--DEBUG.add_msg("childname:"..childname)
            --
			----local child = g:npc_at(locate)
			--local temp = g:critter_by_id(child_id)
			--DEBUG.add_msg("temp:"..temp:disp_name())
            --
			--local child = g:npc_at(temp:pos())
			--DEBUG.add_msg("child:"..child:disp_name())
            --
			--child.name = childname
			--DEBUG.add_msg("child:"..child:disp_name())
            --
			----男で"貧乳"や"巨乳"を持ってたら取り除く。
			--if (child.male) then
			--	if (child:has_trait(trait_id("SMALL_BREAST"))) then
			--		child:unset_mutation(trait_id("SMALL_BREAST"))
			--	end
			--	if (child:has_trait(trait_id("BIG_BREAST"))) then
			--		child:unset_mutation(trait_id("BIG_BREAST"))
			--	end
			--end

			mother:mod_moves(-100)

		end

	end

end


追加 - HENTAI

authors: 溜まり場の変態&有志の方々

description: お気に入りのNPCともっと*親密*な関係になりたい?エロチックなモンスター達に(いろんな意味で)襲われたい?そんなHENTAI要素を追加します。

category: magical


code:
require("./data/mods/Hentai/lua/util")
require("./data/mods/Hentai/lua/const")
require("./data/mods/Hentai/lua/activity")
require("./data/mods/Hentai/lua/monattack")
require("./data/mods/Hentai/lua/anthro")
require("./data/mods/Hentai/lua/special_event")

local is_love_sex			--愛のある行為かどうか

--[[monsterのspeciesが"CUBI"かどうか判定する]]--
function is_cubi(monster)
	DEBUG.add_msg("monster:"..monster:disp_name())

	log.message("14")
	local mtype = monster.type
	DEBUG.add_msg("mtype:"..mtype:nname())

	log.message("18")
	if (mtype:in_species(species_id("CUBI"))) then
	log.message("20")
		return true
	end

	log.message("24")
	return false
end

--[[貞操を失う処理]]--
function lost_virgin(me, is_good)

	if not(me:has_trait(trait_id("VIRGIN"))) then
		return
	end

	if (is_good) then
		add_msg(me:disp_name().."は貞操を失いました！", H_COLOR.LIGHT_GREEN)
	else
		add_msg(me:disp_name().."は貞操を奪われました！", H_COLOR.RED)
	end

	me:unset_mutation(trait_id("VIRGIN"))

end

--[[妊娠判定]]--
function preg_roll(mother)
	--TODO:need more improve

	local preg_chance = PREG_CHANCE

	--発情中なら基礎妊娠確立を+500
	if (mother:has_effect(efftype_id("estrus"))) then
		preg_chance = preg_chance + 500
	end
	--避妊中なら最終的な妊娠確立を/100
	if (mother:has_effect(efftype_id("contraception"))) then
		preg_chance = preg_chance / 100
	end

	DEBUG.add_msg("preg_chance:"..preg_chance)

	if (math.random(100) <= preg_chance) then
		return true
	else
		return false
	end
end

--[[避妊具のiuse処理]]--
function iuse_yiff(item, active)

	--隣接するキャラクタを選択、取得する。
	local center = player:pos()
	local selected_x, selected_y = game.choose_adjacent("誰に対して使用しますか？", center.x, center.y)
	local selected_point = tripoint(selected_x, selected_y, center.z)

	local someone = g:critter_at(selected_point)

	if (someone == nil) then
		game.add_msg("その方向には誰もいない。")
		return
	end

	DEBUG.add_msg(someone:disp_name())

	if (someone:is_monster()) then
		if (someone:has_effect(efftype_id("pet"))) then
			if (game.query_yn(someone:disp_name().."と楽しみますか？")) then
				--TODO:monsterが相手の場合の処理を考えてない
				do_sex(nil, item)
			else
				return
			end
		else
			game.add_msg("やめてください。")
			return
		end

	elseif (someone:is_player()) then
		if (game.query_yn("自分に対して使いますか？")) then
			do_sex(nil, item)
		else
			return
		end

	elseif (someone:is_npc()) then
		--HACK:変数someoneはCritterクラスなのでnpcクラスを再取得してやる
		--local partner = g:npc_at(someone:pos())
		local partner = game.get_npc_at(someone:pos())
		DEBUG.add_msg("id:"..partner:getID())

		local menu = game.create_uimenu()
		local choice = -1

		--パートナーに対して*交渉*を行う。
		menu.title = "行動を選択"

		menu:addentry("何でもない。")									--choice 0
		menu:addentry("気持ちいいことしない？")							--choice 1

		menu:query(true)
		choice = menu.selected

		if (choice == 0) then
			return

		elseif (choice == 1) then
			local is_accept
			is_accept, item = is_accept_u(partner, item)

			if (is_accept) then
				do_sex(partner, item)
			end

		else
			return
		end

	else
		game.add_msg("ERROR!")
		return
	end

	return
end

--[[パートナーが行為を受け入れてくれるかどうかの判定]]--
function is_accept_u(partner, device)
	local is_accept = false

	local willing

	--行為に対するパートナーの積極性・好感度・恐怖度をそれぞれ取得。
	willing = get_willing(partner)

	if (willing > 50) then
		if (is_love_sex) then
			game.popup("大喜びで応じてくれました。")

			--愛がある場合のみ[積極性]％の確立でゴムなしの行為を提案してくる。
			if (math.random(100) <= willing) then
				--TODO:仮にも万人の目に留まる可能性のあるゲームなのだから、本番行為を匂わすメッセージを軽々しく出すのは慎むべき。少なくともオプションで選択できるようにすべき。　と俺の理性が言ってる
				if (game.query_yn(partner:get_name().."は避妊具を使わずに楽しみたいようです。受け入れますか？")) then
					device = nil
				end
			end

		else
			game.popup(partner:get_name().."はあなたへの強い恐怖心により完全に服従しています...")
		end
		is_accept = true

	elseif (willing > 25) then
		if (is_love_sex) then
			game.popup("少し恥ずかしがりながらも応じてくれました。")
		else
			game.popup(partner:get_name().."はあなたへ抱く恐怖心のせいで拒否することができませんでした...")
		end

		is_accept = true
	elseif (willing > 0) then
		game.popup("やんわりと断られました。")
	else
		game.popup("激しく拒否されました。")
	end

	return is_accept, device
end

--[[行為に対するパートナーの積極性を取得する]]--
--TODO:いい感じに積極性の範囲を考える。
--ステータスALL8の@が初期シェルターNPCに話しかけた場合、player:talk_skill()は16, player:intimidation()は10を返す。
function get_willing(partner)
	DEBUG.add_msg("partner:"..partner:get_name())

	--パートナーが敵なら絶対に失敗する。
	if (partner:is_enemy()) then
		return -999, 0, 0
	end

	local willing

	--==プレイヤーに対する好感度・恐怖度を取得する。==--

	--プレイヤーに対する好感の第一印象を取得する。イケメンや話術Skillが高いやつはモテる。
	local trust = player:talk_skill()

	--プレイヤーに対する恐怖感の第一印象を取得する。筋肉教や人を喰うようなやつはコワい。
	local fear = player:intimidation()

	DEBUG.add_msg("trust:"..trust)
	DEBUG.add_msg("fear:"..fear)

	--パートナーの持つ印象度を計算。超適当--
	local opinion = partner.op_of_u
	DEBUG.add_msg("opinion:"..opinion.value)

	--(信頼 * 2) + 価値 - (怒り / 2)を好感度にプラス。
	trust = trust + (opinion.trust * 2) + opinion.value - (opinion.anger / 2)
	--(恐怖 * 2) + (恩義 / 2)を恐怖感にプラス。
	fear = fear + (opinion.fear * 2) + (opinion.owed / 2)

	DEBUG.add_msg("trust_op:"..trust)
	DEBUG.add_msg("fear_op:"..fear)

	--==特質やステータス異常に応じてさらに計算
	--状態異常"drunk"があるならtrustをちょっとだけ上げ、fearをちょっとだけ下げる。倍率は適当
	if (partner:has_effect(efftype_id("drunk"))) then
		trust = trust * 1.2
		fear = fear * 0.8
	end

	--状態異常"estrus"があるならtrust3倍、fear1/3。倍率は適当
	if (partner:has_effect(efftype_id("estrus"))) then
		trust = trust * 3.0
		fear = fear / 3
	end

	--その積極性は好感からか？あるいは恐怖からか？高い方を採用する。
	if (trust >= fear) then
		willing = trust
		is_love_sex = true
	else
		willing = fear
		is_love_sex = false
	end

	--状態異常"corrupt"があるなら強度 * 5をwillingにプラス。倍率は適当
	if (partner:has_effect(efftype_id("corrupt"))) then
		local intensity = partner:get_effect_int(efftype_id("corrupt"))
		willing = willing + intensity * 5
	end

	--特性"VIRGIN"があるならwillingを-30。
	if (partner:has_trait(trait_id("VIRGIN"))) then
		willing = willing - 30
	end

	DEBUG.add_msg("willing:"..willing)

	return willing

end

--[[気持ちいいことの処理]]--
function do_sex(partner, device)

	local pseudo_device

	--pseudo_device = device	--NOTE:ここで受け取るdeviceはたぶんポインタ？なのでそのままグローバルにぶっこむとアクティビティ終了処理にて正しく読み取れない。
								--     ので新しいitemに変換しておく
	if not(device == nil) then
		pseudo_device = item(device)
	else
		pseudo_device = nil
	end

	--行為にかかるターン数を算出。プレイヤーの耐久力(筋力)が多いほどフィニッシュまでの時間が長い。
	local turn_cost = SEX_BASE_TURN * player.str_cur

	--最大ターン数を超えた場合は調整する。でないと成長modとかで筋肉教になった@さんは死ぬまでやってしまうので...
	if (turn_cost > SEX_MAX_TURN) then
		turn_cost = SEX_MAX_TURN
	end

	DEBUG.add_msg("turn_cost:"..turn_cost)

	--行為による意欲ボーナスを算出。

	--単純にプレイヤーかパートナーの器用さで大きい方。単純すぎ？
	local fun_base = player.dex_cur
	if not(partner == nil) then
		if (player.dex_cur < partner.dex_cur) then
			fun_base = partner.dex_cur
		end
	end

	DEBUG.add_msg("fun_base:"..fun_base)

	local sex_fun_bonus
	sex_fun_bonus = fun_base / 2

	--行為のアクティビティ開始。
	SEX.init(sex_fun_bonus, partner, pseudo_device, is_love_sex)
	player:assign_activity(activity_id("ACT_SEX"), turn_cost * player:get_speed() + 1000, 0, 0, "")	--ターン数*プレイヤーの速度にすることで時間ちょうどのmovecostを求められる

	game.add_msg("<color_pink>*しばらくおまちください*</color>")

	--パートナーがいる場合のみ貞操を失う。
	if not(partner == nil) then
		lost_virgin(player, true)
		lost_virgin(partner, true)
	end
end

--[[降魔のチョーカーのiuse処理]]--
function iuse_pet_cubi(item, active)
	--隣接するキャラクタを選択、取得する。
	local center = player:pos()
	local selected_x, selected_y = game.choose_adjacent("誰に対して使用しますか？", center.x, center.y)
	local selected_point = tripoint(selected_x, selected_y, center.z)

	local monster = game.get_monster_at(selected_point)

	if (monster == nil) then
		game.add_msg("それは無理だ。")
		return
	end

	if not(is_cubi(monster)) then
		game.add_msg("使える対象は夢魔だけです。")
		return
	end

	if (monster.friendly == -1) then
		game.add_msg("既にあなたの仲間です。")
		return
	end

	if (monster.friendly > 0) then
		game.add_msg(monster:disp_name().."の気が緩んだ一瞬の隙をついて、チョーカーを身につけさせました！")

		monster.friendly = -1
		monster:add_effect(efftype_id("pet"), game.get_time_duration(1), "num_bp", true)
		monster:disable_special("WIFE_U")

		game.add_msg(monster:disp_name().."はあなたの仲間になった！")

		--プレイヤーの所持品からアイテムを取り除き、モンスターに渡す。
		if (player:has_item(item)) then
			monster:add_item(item)
			player:i_rem(item)
		end
	else
		game.add_msg("チョーカーを"..monster:disp_name().."の首につけようとしましたが、抵抗されました。気の緩んだ隙ならばあるいは...")
	end

end

--[[性転換の霊薬のiuse処理]]--
function iuse_ts_elixir(item, active)

	DEBUG.add_msg("--TRANS--")

	--TODO:LUA拡張iuse処理にてアイテムの使用者を取得する方法を考える
	local target = player

	DEBUG.add_msg(target:disp_name())

	--使用者が妊娠中なら効果は無い。行き場所が無くなっちゃうので
	if (target:has_effect(efftype_id("fertilize")) or target:has_effect(efftype_id("pregnantcy"))) then
		game.add_msg("薬を飲み干しましたが、何も効果はありませんでした。")

		if (player:has_item(item)) then
			player:i_rem(item)
		end

		return
	end

	target:mod_pain(math.random(200))

	if (target.male) then
		target.male = false
		add_msg("下半身を襲った激痛に思わず手をやると、そこにあるはずのものがありませんでした！", H_COLOR.YELLOW)
		add_msg(target:disp_name().."は今や女性です！", H_COLOR.GREEN)
	else
		target.male = true
		add_msg("下半身を襲った激痛に思わず手をやると、そこには無いはずのものがありました！", H_COLOR.YELLOW)
		add_msg(target:disp_name().."は今や男性です！", H_COLOR.GREEN)
	end

	if (player:has_item(item)) then
		player:i_rem(item)
	end

	return
end

--[[名前の巻物のiuse処理]]--
function iuse_naming_npc(item, active)

	DEBUG.add_msg("--NAMING--")

	--隣接するキャラクタを選択、取得する。
	local center = player:pos()
	local selected_x, selected_y = game.choose_adjacent("誰に対して使用しますか？", center.x, center.y)
	local selected_point = tripoint(selected_x, selected_y, center.z)

	--local someone = g:npc_at(selected_point)
	local someone = game.get_npc_at(selected_point)

	if (someone == nil) then
		game.add_msg("その方向には誰もいない。")
		return
	end
	if not(someone:is_npc()) then
		game.add_msg("使える対象はNPCのみです。")
		return
	end

	DEBUG.add_msg("someone:"..someone:disp_name())

	--入力ウィンドウを表示し、入力された内容を取得する。
	local newname = game.string_input_popup("新しい名前を入力してください。", 0, "")
	if (newname == "") then
		game.add_msg("キャンセルしました。")
		return
	end
	DEBUG.add_msg("newname:"..newname)

	someone.name = newname
	game.add_msg("『"..newname.."』と命名しました。")

	if (player:has_item(item)) then
		player:i_rem(item)
	end

	return
end

--[[血清(人間)のiuse処理]]--
function iuse_anthromorph(item, active)

	DEBUG.add_msg("--ANTHRO--")

	--隣接するキャラクタを選択、取得する。
	local center = player:pos()
	local selected_x, selected_y = game.choose_adjacent("誰に対して使用しますか？", center.x, center.y)
	local selected_point = tripoint(selected_x, selected_y, center.z)

	local monster = game.get_monster_at(selected_point)

	if (monster == nil) then
		game.add_msg("その方向には誰もいない。")
		return
	end
	if not(monster:is_monster()) then
		game.add_msg("それは無理だ。")
		return
	end
	if (monster.friendly > -1) then
		game.add_msg("ペット以外に使う事はできません。")
		return
	end

	ANTHRO.main(monster, selected_point)

	if (player:has_item(item)) then
		player:i_rem(item)
	end

	player:mod_moves(-200)

	return
end

--[[アーティファクト生成のiuse処理]]--
function iuse_spawn_artifact(item, active)

	DEBUG.add_msg("--SPAWN_ARTIFACT-- start")

	map:spawn_artifact(player:pos())

	game.add_msg("一瞬、世界が歪んだような奇妙な感覚がしました。")

	if (player:has_item(item)) then
		player:i_rem(item)
	end

	player:mod_moves(-100)

	DEBUG.add_msg("--SPAWN_ARTIFACT-- end")

	return
end

function on_preload()
	-- 乱数の初期化
	math.randomseed(os.time())

	--[[iuse処理の追加]]--
	--game.register_iuse("IUSE_YIFF", iuse_hoge)
	game.register_iuse("IUSE_YIFF", iuse_yiff)
	game.register_iuse("IUSE_PET_CUBI", iuse_pet_cubi)
	game.register_iuse("IUSE_TS_ELIXIR", iuse_ts_elixir)
	game.register_iuse("IUSE_NAMING_NPC", iuse_naming_npc)
	game.register_iuse("IUSE_ANTHROPOMORPH", iuse_anthromorph)
	game.register_iuse("IUSE_SPAWN_ARTIFACT", iuse_spawn_artifact)

	--[[activity処理の追加]]--
	--game.register_activity_do_turn("ACT_SEX", SEX.act_sex_do_turn)
	--game.register_activity_finish("ACT_SEX", SEX.act_sex_finish)

	--[[モンスター特殊攻撃の追加]]--
	game.register_monattack("VULGAR_SPEECH", matk_vulgar_speech)
	game.register_monattack("SEDUCE", matk_seduce)
	game.register_monattack("THROW_KISS", matk_tkiss)
	game.register_monattack("STRIP_U", matk_stripu)
	game.register_monattack("WIFE_U", matk_wifeu)
	game.register_monattack("LOVE_FLAME", matk_loveflame)
	game.register_monattack("EXPOSE", matk_expose)
	game.register_monattack("MAGIC_SUCCUBI_SOMNO", matk_magic_succubi_somnophilia)
	game.register_monattack("MAGIC_GOATHEAD_DEMON", matk_magic_goathead_demon)

	--[[ここは特別なイベント処理をモンスター特殊攻撃で擬似的に発火させる処理の追加]]
	game.register_monattack("EVENT_GOATHEAD_DEMON", event_goathead_demon)
	game.register_monattack("EVENT_DEMONBEING_SCHOOLGIRL", event_demonbeing_schoolgirl)
end

on_preload()


compatibility:
Custom Tileset
MshockXottoplus
MSX++DEAD_PEOPLE


code:
Q:������MOD
A:MOD���L�̒�����json�̋L�q�Ƃ�lua�Ƃ��V�v�f�Ƃ��F�X�g���Ă݂āu�ւ��IMOD���Ă���Ȏ����ł���񂾁I������������ƍ���Ă݂悤���ȁI�v�Ǝv�����v���C���[���ꏏ��MOD����҂̓��ɑƂ��Ă�낤�Ƃ������_�̂��߂ɍ���܂����B
�@���ƃG���B

Q:�o�O���������I
A:����mod�͂��傭���傭�d�l���ς��̂Ŏ����ł��o�O�������͎̂��o���Ă܂�...���k�ł����X���ŕ񍐂��Ē�����΍K���ł��B

Q:NPC�̈˗��œ������e���ɗ������ǁA�˗��������ł��Ȃ��I
A:����킵���ł����ړI�n�͓���*�ی�{��*�ł��B*���e��*�ł͂Ȃ��B

Q:*���g�p*�̔�D����Ăǂ��ɂ���́H
A:��ɖ��ƂȂǂ̃x�b�h���[���Ō�������@�����͂��ł��B�T�L���o�X�^�C���L���o�X�B�������Ă��܂��B

Q:*�C������������*��U������f���܂���...
A:�v���C���[�ɑ΂���D���x�A�������͋��|�S�����ȏ㖳���ƒf���܂��B
�@�@�D���x�F�m�́A���A��b�X�L���A�v���C���[�ւ̐M���x�Ȃǂ��e���B
�@�@���|�S�F�ؗ́A�X���A�e���������܂ܘb��������A�v���C���[�ւ̋��|�Ȃǂ��e���B

�@�ǂ�ȃu�T�����ł��N�G�X�g��1��2��������Ί��Ń����Ă������x�̃o�����X�ɂ͂��Ă���̂ł����S���������B
�@�ł��呀������Ă�q�ɂ͗D�������Ă����Ă��������B�ʂɗD��������ȊO�̎�i�ł��\���܂��񂪁B

Q:�D�P�����Ⴂ�܂���...
A:�i��̎l�����E�@�R���rAA�j

Q:�T�L���o�X�^�C���L���o�X���Ăǂ��ɂ���́H
A:��ɐX�╽����������Ă���͂��ł��B������钆�̕����o���m���������ł��B
�@�]���r�B�Ƃ͓G�΂��Ă��邽�߁A�X���Ō������鎖�͂܂�ł��傤�B

Q:�T�L���o�X�^�C���L���o�X��*�C������������*�������I
A:���R�Ɉړ��ł��Ȃ���ԁi�͂܂�A�w偂̑��A�����Aetc..�j����*����U��*���g���Ă���\��������܂��B�����Ă���Ȃ��̂������ꕔ�ɂ͂��܂����B

Q:����ς胄���Ă���Ȃ��I*�C������������*�ł��Ȃ��I
A:���܂��́@�ς���@�ʂ�

Q:�j�������ǃC���L���o�X�Ƀ����܂����I
A:�d�l�ł��B

Q:���������ǃT�L���o�X�Ƀ����܂�����I
A:�d�l�ł��B

Q:�j���ł��n���E�����̓������擾�ł���񂾂��ǁI�H
A:���ꂾ���͖{���Ɏd�l�ł��B�j���̏ꍇ�͌䗧�h�l�Ƃ����炵��()�Ƃ��݂����ɔ]���ϊ����Ă��������B

Q:��ʂ̃T�L���o�X�^�C���L���o�X�ɕ����Ȃ�Ȃ����O�̃o���G�[�V����������񂾂��ǁA������ĉ��H
A:����ł��B�i*�{������*�j��https://ja.wikipedia.org/wiki/%E6%80%A7%E7%9A%84%E5%80%92%E9%8C%AF
�@�����ȁE�a�I�����ɑ΂���ᔻ��ے�Ƃ������Ӑ}�͈�؂���܂���B�{���ɁB

Q:���̃^�C���G�͈�́I�H
A:�X���̐e�؂ȕ��X���璸�����x���^�C���G�ł��B�R�C�c�̓T�C�R�[�ɃS�L�Q���ȃu�c�������A�Z��I

Q:�ǂ�����18�֗v�f��������ł����I�I�I�I�I�I
A:����Ȃ��Ƃ�����kevin�ɂ߂��I�Ă��ꂿ�Ⴄ����I�I�I�I�I�I
�@�^�ʖڂȘb�A�Z���V�u�ȓ��e�͊C�O�̂ق������邳�����낤����kevin����̋t�؂ɐG��Ă���MOD���̂��̂�BAN����Ȃ����S�z�ł͂���̂ł��B���⍑���ł�18+�ȓ��e��s���葽���Ɍ��J����̂̓A�������ǁB...���v����Ȃ���MOD�H

Q:�X�J�v�f��...
A:�m�[�B�P���Ƀm�[�B���Ƃ�mod�ł����Ă��B
�@...����kevin����Forum�ŃR�����g���Ă��B
�@https://discourse.cataclysmdda.org/t/fms-frequently-made-suggestions/9764
�@�ł��̂ŁA��������mod�ł͂��܂�ϋɓI�ɂ��C�͂���܂���B															����������mod�����ρE�Ĕz�z����ȂƂ͌����Ă��Ȃ��B���̈Ӗ������킩��H


code:
====================
�͂��߂�
====================
������CDDA�{�̂ł����삵�܂��B���̂͂��ł��B

0.C-8204�Ńe�X�g�v���C���Ă��܂��B����ȍ~�ł����Ԃ񓮂��Ǝv���܂��B�i���ӔC�j
NPC�̉�b����Ƃ��d�ʂ̋L�q�Ƃ��A���ƍŋ߂Ɏ������ꂽ�v�f���g���Ă�̂ŁA����Ȃ�ɌÂ��o�[�W�������ƃG���[���������邩������܂���B�����ӂ��B

====================
�������@
====================
�uHentai�v�t�H���_��cataclysmdda\data\mods�ɔz�u���ĉ������B
���t�H���_����ύX����Ɛ���ɓ��삵�܂���B�������ς������ꍇ��preload.lua�̓���ւ�ɂ���t�H���_�p�X�����������ɕҏW���ĉ������BUTF-8�`���łˁI

====================
����MOD�ɂ���
====================
�ENPC��y�b�g�Ƃ�*�C������������*���ł���悤�ɂȂ�܂��B
�E*�C������������*���d�|���Ă���G��_���W�������ǉ�����܂��B
�E*�C������������*�ɂ��D�P����悤�ɂȂ�܂��B��D���Ă��Ȃ���΂̘b�ł����B
�EHENTAI�I�ȃA�C�e������������낢��ƒǉ�����܂��B
�E����̃y�b�g��NPC�ɕω������邱�Ƃ��ł���悤�ɂȂ�܂��B�[�l���I�Ȋ����B
�@�����p�̐E�Ɓi�u��l�ƈ�C�v�j����ǉ������̂Ŏ����葁�������Ă݂������͂ǂ����B

NPC��*�C������������*���s���ꍇ�A�A�C�e���u��D��v��NPC�ɑ΂��Ďg�p���ĉ������B
�Ώۂɑ΂��ċ����e���͂������Ă���Ή����Ă����͂��ł��B

18�֗v�f�͂���܂���B�ł�15�ւ��炢��������܂���B�@���Ƃ����g�̃��X�N���l���Ă����p�������B
���ƁA��ƃL�[�{�[�h�͐����ɂ��܂��傤�B

====================
����̗\��
====================
���������Ђ�肽��������
�E�����n�ψفAHENTAI�ȓ����������ƒǉ��BLUA���������ĂȂ񂩂ł��Ȃ����ȁ[�Ǝv���Ă܂����̐S�̃l�^���v�����Ȃ��B
�E���j�[�N�ȓG�L������_���W�����A�C�x���g�̒ǉ��B
�E���m�̃o�O�̏C���B�����X�^�[��*���y����*���Ƀ����X�^�[������ł��܂��ƃ��b���Ă�t���O���������Ɏc���Ă��܂��B
�@�c�O�Ȃ��炢���������@���v���Ȃ�...���͉��}���u�Ƃ��ăQ�[����1���Ԃ��ƂɃt���O�����Z�b�g����悤�ɂ��Ă��܂��B

�������ł���΂�肽��������
�E�������̕����Ƃ��喡�ȂƂ��Ƃ��𒲐��������B
�EHENTAI�v�f�̃I�v�V�����ݒ��u���b�N���X�g�B�G��������\���������Ȑl�������ˁB...�����ˁH�@�ł������ڐ�̂�肽�����Ƃ��������ėD��x�͈�Ԍ�񂵂ł��B

�������i���̉ۑ聫����
�E�Q�[���o�����X�̒���
�E�R�[�h�̎蒼��

====================
Version
====================
��Ver1.4		CDDA 0.C-8204�݊�
�@�E�{�X��75���ڂ�>>244�Ō��J���ꂽ�^�C���Z�b�g��ǉ��B�i�����X�^�[2��A�A�C�e��4��j
�@�E�{�X��75���ڂ�>>549�Ŋm�F�����s��iKawaiiMaidMod�Ƃ̋����j���C���B
�@�E�{��CDDA�����ƁA�T�L���o�X�E�\���m�̓���U���ƔD�P�̏���������ɓ��삵�Ȃ��s����C���B�f�o�b�O������Ă܂���ł�����...
�@�E�V�����X�^�[��������Ƃ����ǉ��B
�@�E��Ԉُ�"��"�A"���@�̖��C"�̌��ʎ��Ԃɏ����ݒ�B����Ȃ����ăX���ł������������Ă��̂ŁB
�@�E�T�L���o�X�^�C���L���o�X�̓���U��(�E�����E�U�f)�ɉ�𔻒菈����ǉ��B
�@�@�E�����U���͕����I�ȉ��\�͂ŁA�U�f�͑Ώێ҂̒m�͂ɉ����Ē�R���܂��B
�@�E�T�L���o�X�^�C���L���o�X��*���y����*���A�m���ň����^�C�v�̕ψق���������悤�ɁB
�@�E�C���L���o�X����ł��D�P����悤�ɕύX�B
�@�E�I���W�i���_���W������1�ǉ��B�i�����n��^�c�w�Z�j�@������Ƃ����{�X����ǂ��̃C�x���g�������I
�@�E�T�L���o�X�^�C���L���o�X�Ƃ�*���y����*�͍ő�œ�����2�l�܂�(3P)�Ƃ���悤�ɕύX�B��L�̃I���W�i���_���W�����̃e�X�g�v���C���Ɉ͂܂�Ă����Ɖ��̃^�[���I����Ė������[�v���܂����āu���A�������Ȃ��Ƒʖڂ�����v���Ďv���܂����B
�@�E�T�L���o�X�^�C���L���o�X�̏d�ʂ��蒼���B81.5kg�̃T�L���o�X�̓o�[�W�������Â������A�����ˁH
�@�@�Ȃ��A�d�ʂ̓A�����J�l�̕��ϐg���ɑ΂��A�j��BMI�l��21(66.536kg)�A����BMI�l��20(53.138kg)�Ƃ��Čv�Z�����l�ɂ��Ă���܂��B
�@�@�i���Ȃ݂ɃA�����J�l�j���̕��ς�178cm / 87kg�A�����̕��ς�163cm / 74kg�B����[�L�c�C�����j

��Ver1.3plus2�@�@2018/10/21�z�z		CDDA 0.C-7987�݊�
�@�E�{�X��75���ڂ�>>152�Ō��J���ꂽ�^�C���Z�b�g��ǉ��B�i�T�L���o�X�E�T�f�B�X�g�A�\���m�A�G�N�X�A���N�g(�V�G)�B�j���肪�Ă����肪�Ă�...

��Ver1.3plus�@�@2018/10/21�z�z		CDDA 0.C-7987�݊�
�@�E�{�X��74���ڂŌ��J���ꂽ�^�C���Z�b�g��ǉ��B�i�����X�^�[6��A�A�C�e��2��A�G�t�F�N�g3��B�j�����炪���k���Ă��܂����炢��ʂɕ`���Ē����܂����B�{���Ɋ��ӂ��Ă�������܂���B
�@�E�y�b�g�̃T�L���o�X�^�C���L���o�X��NPC���ł���悤�ɁB
�@�E�o�O�C���B�{��CDDA�����ƁA�T�L���o�X�^�C���L���o�X�̓���U�����t�B�j�b�V���܂œ��B���Ȃ��Ƃ������Ɛ[���ȕs����c���Ă����B

��Ver1.3�@�@2018/10/15�z�z		CDDA 0.C-7987�݊�
�@�E�{��CDDA�ł������悤�ɁB
�@�E�y�b�g��NPC���ł���悤�ɁB���̏��Ώۂ̓C�k�A�l�R�A�N�}�B�������̃l�^����肽�����������B
�@�E�V�����ψكJ�e�S���u�����v��ǉ��B�܂��A���̕ψٖ�/�����̃��V�s���ڂ����w�K�����ǉ��B�X�|�[���ꏊ�͂�����񂠂����ł��B
�@�@�������̐S�̕ψَ��̂��܂����Ȃ�...
�@�E�C���L���o�X�n�̐V�G���������ǉ��B�i�X�e�m�A�t�@���X�A�z�v���B�j�l�^�͑O�X����p�ӂ��Ă����ǃo�����X�����ɔY��ł܂����B����������ƔY��ł�B
�@�E�y�b�g�ɑ΂���*�C������������*�����ۂ̏������o�O���Ă��̂ŏC���B
�@�E���̑��A�C�e������������傱���傱�ǉ������蒲��������B

��Ver1.2�@�@2018/09/15�z�z		LUA�g����CDDA 0.C-7713�݊�
�@�E*�C������������*���A�N�e�B�r�e�B�����i�؂̔��̂�N���t�g�Ȃǂ̒����Ԃɋy�ԍs���j�ɕύX�B�܂�r���ŃL�����Z���ł���悤�ɁB
�@�E�T�L���o�X�n�̐V�G���������ǉ��B�i�T�f�B�X�g�A�\���m�A�G�N�X�A���N�g�j
�@�E�D�P�v�f�̒ǉ��B����Ɋւ��A�C�e���i��D��j���ǉ��B�D�P�ł���͍̂��̏��v���C���[��NPC�Ԃ����B
�@�E�������y�b...���Ԃɂł���A�C�e����ǉ��B
�@�EHENTAI�ȓ������������ǉ��B������ƃt�F�`���΂肷����������B
�@�E�T�L���o�X�A�C���L���o�X��|�����ۂ̈ӗ~�y�i���e�B��啝�Ɋɘa�B�l�^�Ƃ͂����A�P���|�����Ă��鈫����|���������őΐl����Ɠ����y�i���e�B���󂯂�̂͂������ɂ�肷�����Ǝv���܂����B
�@�E�{�X��70���ڂ�>>55�Ō��J���ꂽ�^�C���摜�t�@�C���𓯍��BLUA�g����CDDA�Ƃ�������Ƃ����A�L�u�̕��ɂ͖{���ɓ����オ��܂���B
�@�E���̑����܂��܂����������蒼���B

��Ver1.1�@�@2018/08/11�z�z
�@�E�T�L���o�X�n�A�C���L���o�X�n�����X�^�[��V�K�ǉ��B
�@�E�Ǝ���LUA�����X�^�[����U�����������V�K�ǉ��B
�@�E�v���C���[�ɑ΂���D���x�̌v�Z�����ԈႢ�܂����Ă��̂��C���B
�@�E*�C������������*�̌�Ɏ擾�ł���A�C�e�������̏�ŕ����Ă��܂��s����C���B
�@�E�x�b�h�ɐ���������D��̐�������������Ƃ�������C���B

��Ver1.0�@�@2018/07/14�z�z
�@�E�V�K�J���B

path: original documentation/readme.txt


code:
2019/05/10
�G���[���b�Z�[�W���o��ӏ���nil�`�F�b�N��ǉ��B�ق��ɂ����邩���B

2019/05/07
LUAEX��0.D-8918�œ��삷��悤��can_wife�֐��̃o�O�炵�����̂��C��
�i0.D����ł��Ƃ���œ�����ł����ˁH�j
�����X�^�[�̐ݒ�json��0.D�J���ł̃t�H�[�}�b�g�ɍ��킹�ďC���B

���ρFlispcoc(https://github.com/lispcoc)

path: original documentation/readme改.txt


code:
====================
�v��
====================
�E����MOD�̗��p�ɂ��āF�D���Ɏg���Ă�����I
�E�����E���ρE�Ĕz�z�F�D���ɂ��Ă�����I
�E���C�Z���X�F����MOD���̂�CC 0�i�p�u���b�N�h���C���j�ł��B
�@�ł��{�Ƃɍ��킹��ꍇ�͓������C�Z���X��CC BY-SA�ɍ��킹�Ȃ��Ⴂ���Ȃ���ł����ˁH�����Ƃ肠����������ۂ��������Ă邾���ł悭�킩���Ă܂���B

====================
CC 0�ɂ���
====================
���L���Q�l�ɂ��ĉ������B
https://creativecommons.jp/sciencecommons/aboutcc0/

====================
���̃v���O�����͉��LCataclysm: Dark Days Ahead��MOD�ł��B
https://cataclysmdda.org/

�܂��A�ꕔ�@�\�͗L�u�̕��ɂ��CDDA LUA�g����exe�Ɉˑ����Ă��܂��B
https://lispcoc.github.io/cdda_junkbox/mods/mods.html

path: original documentation/ライセンス.txt


code:
tags
Setup
*.hi
*.o
tmp
test
build-stamp
Build/SysConfig.hs
Build/InstallDesktopFile
Build/EvilSplicer
Build/Standalone
Build/OSXMkLibs
Build/LinuxMkLibs
Build/BuildVersion
git-annex
git-annex.1
git-annex-shell.1
git-union-merge
git-union-merge.1
doc/.ikiwiki
html
*.tix
.hpc
dist
# Sandboxed builds
cabal-dev
.cabal-sandbox
cabal.sandbox.config
cabal.config
# Project-local emacs configuration
.dir-locals.el
# OSX related
.DS_Store
.virthualenv
.tasty-rerun-log

repo_name: kakafarm/git

path: .gitignore


code:
Joey Hess <joey@kitenet.net> http://joey.kitenet.net/ <joey@web>
Joey Hess <joey@kitenet.net> http://joeyh.name/ <joey@web>
Joey Hess <joey@kitenet.net> http://joeyh.name/ <http://joeyh.name/@web>
Yaroslav Halchenko <debian@onerussian.com>
Yaroslav Halchenko <debian@onerussian.com> http://yarikoptic.myopenid.com/ <site-myopenid@web>
Yaroslav Halchenko <debian@onerussian.com> https://www.google.com/accounts/o8/id?id=AItOawnx8kHW66N3BqmkVpgtXDlYMvr8TJ5VvfY <Yaroslav@web>

path: .mailmap


code:
{- git-annex monad
 -
 - Copyright 2010-2013 Joey Hess <joey@kitenet.net>
 -
 - Licensed under the GNU GPL version 3 or higher.
 -}

{-# LANGUAGE CPP, GeneralizedNewtypeDeriving, PackageImports #-}

module Annex (
	Annex,
	AnnexState(..),
	new,
	run,
	eval,
	getState,
	changeState,
	setFlag,
	setField,
	setOutput,
	getFlag,
	getField,
	addCleanup,
	gitRepo,
	inRepo,
	fromRepo,
	calcRepo,
	getGitConfig,
	changeGitConfig,
	changeGitRepo,
	getRemoteGitConfig,
	withCurrentState,
) where

import Common
import qualified Git
import qualified Git.Config
import Annex.Direct.Fixup
import Git.CatFile
import Git.CheckAttr
import Git.CheckIgnore
import Git.SharedRepository
import qualified Git.Hook
import qualified Git.Queue
import Types.Key
import Types.Backend
import Types.GitConfig
import qualified Types.Remote
import Types.Crypto
import Types.BranchState
import Types.TrustLevel
import Types.Group
import Types.Messages
import Types.UUID
import Types.FileMatcher
import Types.NumCopies
import Types.LockPool
import Types.MetaData
import Types.DesktopNotify
import Types.CleanupActions
#ifdef WITH_QUVI
import Utility.Quvi (QuviVersion)
#endif
import Utility.InodeCache

import "mtl" Control.Monad.Reader
import Control.Monad.Catch
import Control.Concurrent
import qualified Data.Map as M
import qualified Data.Set as S

{- git-annex's monad is a ReaderT around an AnnexState stored in a MVar.
 - This allows modifying the state in an exception-safe fashion.
 - The MVar is not exposed outside this module.
 -}
newtype Annex a = Annex { runAnnex :: ReaderT (MVar AnnexState) IO a }
	deriving (
		Monad,
		MonadIO,
		MonadReader (MVar AnnexState),
		MonadCatch,
		MonadThrow,
#if MIN_VERSION_exceptions(0,6,0)
		MonadMask,
#endif
		Functor,
		Applicative
	)

-- internal state storage
data AnnexState = AnnexState
	{ repo :: Git.Repo
	, gitconfig :: GitConfig
	, backends :: [BackendA Annex]
	, remotes :: [Types.Remote.RemoteA Annex]
	, remoteannexstate :: M.Map UUID AnnexState
	, output :: MessageState
	, force :: Bool
	, fast :: Bool
	, auto :: Bool
	, daemon :: Bool
	, branchstate :: BranchState
	, repoqueue :: Maybe Git.Queue.Queue
	, catfilehandles :: M.Map FilePath CatFileHandle
	, checkattrhandle :: Maybe CheckAttrHandle
	, checkignorehandle :: Maybe (Maybe CheckIgnoreHandle)
	, forcebackend :: Maybe String
	, globalnumcopies :: Maybe NumCopies
	, forcenumcopies :: Maybe NumCopies
	, limit :: ExpandableMatcher Annex
	, uuidmap :: Maybe UUIDMap
	, preferredcontentmap :: Maybe (FileMatcherMap Annex)
	, requiredcontentmap :: Maybe (FileMatcherMap Annex)
	, shared :: Maybe SharedRepository
	, forcetrust :: TrustMap
	, trustmap :: Maybe TrustMap
	, groupmap :: Maybe GroupMap
	, ciphers :: M.Map StorableCipher Cipher
	, lockpool :: LockPool
	, flags :: M.Map String Bool
	, fields :: M.Map String String
	, modmeta :: [ModMeta]
	, cleanup :: M.Map CleanupAction (Annex ())
	, sentinalstatus :: Maybe SentinalStatus
	, useragent :: Maybe String
	, errcounter :: Integer
	, unusedkeys :: Maybe (S.Set Key)
#ifdef WITH_QUVI
	, quviversion :: Maybe QuviVersion
#endif
	, existinghooks :: M.Map Git.Hook.Hook Bool
	, desktopnotify :: DesktopNotify
	}

newState :: GitConfig -> Git.Repo -> AnnexState
newState c r = AnnexState
	{ repo = r
	, gitconfig = c
	, backends = []
	, remotes = []
	, remoteannexstate = M.empty
	, output = defaultMessageState
	, force = False
	, fast = False
	, auto = False
	, daemon = False
	, branchstate = startBranchState
	, repoqueue = Nothing
	, catfilehandles = M.empty
	, checkattrhandle = Nothing
	, checkignorehandle = Nothing
	, forcebackend = Nothing
	, globalnumcopies = Nothing
	, forcenumcopies = Nothing
	, limit = BuildingMatcher []
	, uuidmap = Nothing
	, preferredcontentmap = Nothing
	, requiredcontentmap = Nothing
	, shared = Nothing
	, forcetrust = M.empty
	, trustmap = Nothing
	, groupmap = Nothing
	, ciphers = M.empty
	, lockpool = M.empty
	, flags = M.empty
	, fields = M.empty
	, modmeta = []
	, cleanup = M.empty
	, sentinalstatus = Nothing
	, useragent = Nothing
	, errcounter = 0
	, unusedkeys = Nothing
#ifdef WITH_QUVI
	, quviversion = Nothing
#endif
	, existinghooks = M.empty
	, desktopnotify = mempty
	}

{- Makes an Annex state object for the specified git repo.
 - Ensures the config is read, if it was not already. -}
new :: Git.Repo -> IO AnnexState
new r = do
	r' <- Git.Config.read r
	let c = extractGitConfig r'
	newState c <$> if annexDirect c then fixupDirect r' else return r'

{- Performs an action in the Annex monad from a starting state,
 - returning a new state. -}
run :: AnnexState -> Annex a -> IO (a, AnnexState)
run s a = do
	mvar <- newMVar s
	r <- runReaderT (runAnnex a) mvar
	s' <- takeMVar mvar
	return (r, s')

{- Performs an action in the Annex monad from a starting state,
 - and throws away the new state. -}
eval :: AnnexState -> Annex a -> IO a
eval s a = do
	mvar <- newMVar s
	runReaderT (runAnnex a) mvar

getState :: (AnnexState -> v) -> Annex v
getState selector = do
	mvar <- ask
	s <- liftIO $ readMVar mvar
	return $ selector s

changeState :: (AnnexState -> AnnexState) -> Annex ()
changeState modifier = do
	mvar <- ask
	liftIO $ modifyMVar_ mvar $ return . modifier

{- Sets a flag to True -}
setFlag :: String -> Annex ()
setFlag flag = changeState $ \s ->
	s { flags = M.insertWith' const flag True $ flags s }

{- Sets a field to a value -}
setField :: String -> String -> Annex ()
setField field value = changeState $ \s ->
	s { fields = M.insertWith' const field value $ fields s }

{- Adds a cleanup action to perform. -}
addCleanup :: CleanupAction -> Annex () -> Annex ()
addCleanup k a = changeState $ \s ->
	s { cleanup = M.insertWith' const k a $ cleanup s }

{- Sets the type of output to emit. -}
setOutput :: OutputType -> Annex ()
setOutput o = changeState $ \s ->
	s { output = (output s) { outputType = o } }

{- Checks if a flag was set. -}
getFlag :: String -> Annex Bool
getFlag flag = fromMaybe False . M.lookup flag <$> getState flags

{- Gets the value of a field. -}
getField :: String -> Annex (Maybe String)
getField field = M.lookup field <$> getState fields

{- Returns the annex's git repository. -}
gitRepo :: Annex Git.Repo
gitRepo = getState repo

{- Runs an IO action in the annex's git repository. -}
inRepo :: (Git.Repo -> IO a) -> Annex a
inRepo a = liftIO . a =<< gitRepo

{- Extracts a value from the annex's git repisitory. -}
fromRepo :: (Git.Repo -> a) -> Annex a
fromRepo a = a <$> gitRepo

{- Calculates a value from an annex's git repository and its GitConfig. -}
calcRepo :: (Git.Repo -> GitConfig -> IO a) -> Annex a
calcRepo a = do
	s <- getState id
	liftIO $ a (repo s) (gitconfig s)

{- Gets the GitConfig settings. -}
getGitConfig :: Annex GitConfig
getGitConfig = getState gitconfig

{- Modifies a GitConfig setting. -}
changeGitConfig :: (GitConfig -> GitConfig) -> Annex ()
changeGitConfig a = changeState $ \s -> s { gitconfig = a (gitconfig s) }

{- Changing the git Repo data also involves re-extracting its GitConfig. -}
changeGitRepo :: Git.Repo -> Annex ()
changeGitRepo r = changeState $ \s -> s
	{ repo = r
	, gitconfig = extractGitConfig r
	}

{- Gets the RemoteGitConfig from a remote, given the Git.Repo for that
 - remote. -}
getRemoteGitConfig :: Git.Repo -> Annex RemoteGitConfig
getRemoteGitConfig r = do
	g <- gitRepo
	return $ extractRemoteGitConfig g (Git.repoDescribe r)

{- Converts an Annex action into an IO action, that runs with a copy
 - of the current Annex state.
 -
 - Use with caution; the action should not rely on changing the
 - state, as it will be thrown away. -}
withCurrentState :: Annex a -> Annex (IO a)
withCurrentState a = do
	s <- getState id
	return $ eval s a


code:
{- git-annex automatic merge conflict resolution
 -
 - Copyright 2012-2014 Joey Hess <joey@kitenet.net>
 -
 - Licensed under the GNU GPL version 3 or higher.
 -}

module Annex.AutoMerge
	( autoMergeFrom
	, resolveMerge
	, commitResolvedMerge
	) where

import Common.Annex
import qualified Annex.Queue
import Annex.Direct
import Annex.CatFile
import Annex.Link
import qualified Git.LsFiles as LsFiles
import qualified Git.UpdateIndex as UpdateIndex
import qualified Git.Merge
import qualified Git.Ref
import qualified Git
import qualified Git.Branch
import Git.Types (BlobType(..))
import Config
import Annex.ReplaceFile
import Git.FileMode
import Annex.VariantFile

import qualified Data.Set as S

{- Merges from a branch into the current branch
 - (which may not exist yet),
 - with automatic merge conflict resolution.
 -
 - Callers should use Git.Branch.changed first, to make sure that
 - there are changed from the current branch to the branch being merged in.
 -}
autoMergeFrom :: Git.Ref -> (Maybe Git.Ref) -> Git.Branch.CommitMode -> Annex Bool
autoMergeFrom branch currbranch commitmode = do
	showOutput
	case currbranch of
		Nothing -> go Nothing
		Just b -> go =<< inRepo (Git.Ref.sha b)
  where
	go old = ifM isDirect
		( mergeDirect currbranch old branch (resolveMerge old branch) commitmode
		, inRepo (Git.Merge.mergeNonInteractive branch commitmode)
			<||> (resolveMerge old branch <&&> commitResolvedMerge commitmode)
		)

{- Resolves a conflicted merge. It's important that any conflicts be
 - resolved in a way that itself avoids later merge conflicts, since
 - multiple repositories may be doing this concurrently.
 -
 - Only merge conflicts where at least one side is an annexed file
 - is resolved.
 -
 - This uses the Keys pointed to by the files to construct new
 - filenames. So when both sides modified annexed file foo,
 - it will be deleted, and replaced with files foo.variant-A and
 - foo.variant-B.
 -
 - On the other hand, when one side deleted foo, and the other modified it,
 - it will be deleted, and the modified version stored as file
 - foo.variant-A (or B).
 -
 - It's also possible that one side has foo as an annexed file, and
 - the other as a directory or non-annexed file. The annexed file
 - is renamed to resolve the merge, and the other object is preserved as-is.
 -
 - In indirect mode, the merge is resolved in the work tree and files
 - staged, to clean up from a conflicted merge that was run in the work
 - tree.
 -
 - In direct mode, the work tree is not touched here; files are staged to
 - the index, and written to the gitAnnexMergeDir, for later handling by
 - the direct mode merge code.
 -}
resolveMerge :: Maybe Git.Ref -> Git.Ref -> Annex Bool
resolveMerge us them = do
	top <- fromRepo Git.repoPath
	(fs, cleanup) <- inRepo (LsFiles.unmerged [top])
	mergedfs <- catMaybes <$> mapM (resolveMerge' us them) fs
	let merged = not (null mergedfs)
	void $ liftIO cleanup

	unlessM isDirect $ do
		(deleted, cleanup2) <- inRepo (LsFiles.deleted [top])
		unless (null deleted) $
			Annex.Queue.addCommand "rm" [Params "--quiet -f --"] deleted
		void $ liftIO cleanup2

	when merged $ do
		unlessM isDirect $
			cleanConflictCruft mergedfs top
		Annex.Queue.flush
		showLongNote "Merge conflict was automatically resolved; you may want to examine the result."
	return merged

resolveMerge' :: Maybe Git.Ref -> Git.Ref -> LsFiles.Unmerged -> Annex (Maybe FilePath)
resolveMerge' Nothing _ _ = return Nothing
resolveMerge' (Just us) them u = do
	kus <- getkey LsFiles.valUs LsFiles.valUs
	kthem <- getkey LsFiles.valThem LsFiles.valThem
	case (kus, kthem) of
		-- Both sides of conflict are annexed files
		(Just keyUs, Just keyThem)
			| keyUs /= keyThem -> resolveby $ do
				makelink keyUs
				makelink keyThem
			| otherwise -> resolveby $
				makelink keyUs
		-- Our side is annexed file, other side is not.
		(Just keyUs, Nothing) -> resolveby $ do
			graftin them file LsFiles.valThem LsFiles.valThem
			makelink keyUs
		-- Our side is not annexed file, other side is.
		(Nothing, Just keyThem) -> resolveby $ do
			graftin us file LsFiles.valUs LsFiles.valUs
			makelink keyThem
		-- Neither side is annexed file; cannot resolve.
		(Nothing, Nothing) -> return Nothing
  where
	file = LsFiles.unmergedFile u

	getkey select select'
		| select (LsFiles.unmergedBlobType u) == Just SymlinkBlob =
			case select' (LsFiles.unmergedSha u) of
				Nothing -> return Nothing
				Just sha -> catKey sha symLinkMode
		| otherwise = return Nothing

	makelink key = do
		let dest = variantFile file key
		l <- inRepo $ gitAnnexLink dest key
		replacewithlink dest l
		stageSymlink dest =<< hashSymlink l

	replacewithlink dest link = ifM isDirect
		( do
			d <- fromRepo gitAnnexMergeDir
			replaceFile (d </> dest) $ makeGitLink link
		, replaceFile dest $ makeGitLink link
		)

	{- Stage a graft of a directory or file from a branch.
	 -
	 - When there is a conflicted merge where one side is a directory
	 - or file, and the other side is a symlink, git merge always
	 - updates the work tree to contain the non-symlink. So, the
	 - directory or file will already be in the work tree correctly,
	 - and they just need to be staged into place. Do so by copying the
	 - index. (Note that this is also better than calling git-add
	 - because on a crippled filesystem, it preserves any symlink
	 - bits.)
	 -
	 - It's also possible for the branch to have a symlink in it,
	 - which is not a git-annex symlink. In this special case,
	 - git merge does not update the work tree to contain the symlink
	 - from the branch, so we have to do so manually.
	 -}
	graftin b item select select' = do
		Annex.Queue.addUpdateIndex
			=<< fromRepo (UpdateIndex.lsSubTree b item)
		when (select (LsFiles.unmergedBlobType u) == Just SymlinkBlob) $
			case select' (LsFiles.unmergedSha u) of
				Nothing -> noop
				Just sha -> do
					link <- catLink True sha
					replacewithlink item link

	resolveby a = do
		{- Remove conflicted file from index so merge can be resolved. -}
		Annex.Queue.addCommand "rm" [Params "--quiet -f --cached --"] [file]
		void a
		return (Just file)

{- git-merge moves conflicting files away to files
 - named something like f~HEAD or f~branch or just f, but the
 - exact name chosen can vary. Once the conflict is resolved,
 - this cruft can be deleted. To avoid deleting legitimate
 - files that look like this, only delete files that are
 - A) not staged in git and B) look like git-annex symlinks.
 -}
cleanConflictCruft :: [FilePath] -> FilePath -> Annex ()
cleanConflictCruft resolvedfs top = do
	(fs, cleanup) <- inRepo $ LsFiles.notInRepo False [top]
	mapM_ clean fs
	void $ liftIO cleanup
  where
	clean f
		| matchesresolved f = whenM (isJust <$> isAnnexLink f) $
			liftIO $ nukeFile f
		| otherwise = noop
	s = S.fromList resolvedfs
	matchesresolved f = S.member f s || S.member (base f) s
	base f = reverse $ drop 1 $ dropWhile (/= '~') $ reverse f

commitResolvedMerge :: Git.Branch.CommitMode -> Annex Bool
commitResolvedMerge commitmode = inRepo $ Git.Branch.commitCommand commitmode
	[ Param "--no-verify"
	, Param "-m"
	, Param "git-annex automatic merge conflict fix"
	]